| | 1 | #ifdef RCSID |
| | 2 | static char RCSid[] = |
| | 3 | "$Header: d:/cvsroot/tads/tads3/VMOBJ.CPP,v 1.4 1999/07/11 00:46:58 MJRoberts Exp $"; |
| | 4 | #endif |
| | 5 | |
| | 6 | /* |
| | 7 | * Copyright (c) 1998, 2002 Michael J. Roberts. All Rights Reserved. |
| | 8 | * |
| | 9 | * Please see the accompanying license file, LICENSE.TXT, for information |
| | 10 | * on using and copying this software. |
| | 11 | */ |
| | 12 | /* |
| | 13 | Name |
| | 14 | vmobj.cpp - VM object manager |
| | 15 | Function |
| | 16 | |
| | 17 | Notes |
| | 18 | |
| | 19 | Modified |
| | 20 | 10/28/98 MJRoberts - Creation |
| | 21 | */ |
| | 22 | |
| | 23 | #include <stdlib.h> |
| | 24 | #include <memory.h> |
| | 25 | #include <assert.h> |
| | 26 | |
| | 27 | #include "t3std.h" |
| | 28 | #include "vmtype.h" |
| | 29 | #include "vmobj.h" |
| | 30 | #include "vmstack.h" |
| | 31 | #include "vmundo.h" |
| | 32 | #include "vmrun.h" |
| | 33 | #include "vmfile.h" |
| | 34 | #include "vmmeta.h" |
| | 35 | #include "vmlst.h" |
| | 36 | #include "vmstr.h" |
| | 37 | #include "vmintcls.h" |
| | 38 | #include "vmpool.h" |
| | 39 | #include "vmfunc.h" |
| | 40 | #include "vmpredef.h" |
| | 41 | #include "vmhash.h" |
| | 42 | #include "vmtobj.h" |
| | 43 | |
| | 44 | |
| | 45 | /* ------------------------------------------------------------------------ */ |
| | 46 | /* |
| | 47 | * Base fixed-size object entry implementation |
| | 48 | */ |
| | 49 | |
| | 50 | /* |
| | 51 | * Metaclass registration object for the root object class. Note that a |
| | 52 | * root object can never be instantiated; this entry is purely for the |
| | 53 | * use of the type system. |
| | 54 | */ |
| | 55 | static CVmMetaclassRoot metaclass_reg_obj; |
| | 56 | CVmMetaclass *CVmObject::metaclass_reg_ = &metaclass_reg_obj; |
| | 57 | |
| | 58 | /* function table */ |
| | 59 | int (CVmObject:: |
| | 60 | *CVmObject::func_table_[])(VMG_ vm_obj_id_t self, |
| | 61 | vm_val_t *retval, uint *argc, |
| | 62 | vm_prop_id_t prop, vm_obj_id_t *source_obj) = |
| | 63 | { |
| | 64 | &CVmObject::getp_undef, |
| | 65 | &CVmObject::getp_of_kind, |
| | 66 | &CVmObject::getp_sclist, |
| | 67 | &CVmObject::getp_propdef, |
| | 68 | &CVmObject::getp_proptype, |
| | 69 | &CVmObject::getp_get_prop_list, |
| | 70 | &CVmObject::getp_get_prop_params, |
| | 71 | &CVmObject::getp_is_class, |
| | 72 | &CVmObject::getp_propinh, |
| | 73 | &CVmObject::getp_is_transient |
| | 74 | }; |
| | 75 | |
| | 76 | /* |
| | 77 | * Allocate space for an object from a page table, given the object ID. |
| | 78 | * The caller must allocate the object ID prior to new'ing the object; |
| | 79 | * operator new will store the memory for the new object in the object |
| | 80 | * slot the caller allocated. |
| | 81 | */ |
| | 82 | void *CVmObject::operator new(size_t siz, VMG_ vm_obj_id_t obj_id) |
| | 83 | { |
| | 84 | /* |
| | 85 | * The size must be the size of an object entry. This size never |
| | 86 | * changes, even for subclasses of the object type, since all |
| | 87 | * variable-size data must be stored in the variable-size portion of |
| | 88 | * the object. Here we are only concerned with allocating the |
| | 89 | * fixed-size object descriptor. |
| | 90 | */ |
| | 91 | assert(siz == sizeof(CVmObject)); |
| | 92 | |
| | 93 | /* return the memory contained in the object entry */ |
| | 94 | return G_obj_table->get_obj(obj_id); |
| | 95 | } |
| | 96 | |
| | 97 | /* |
| | 98 | * Determine if this object is an instance of the given object. By |
| | 99 | * default, we will simply check to see if the given object is the |
| | 100 | * IntrinsicClass instance that represents our metaclass or one of its |
| | 101 | * superclasses. |
| | 102 | */ |
| | 103 | int CVmObject::is_instance_of(VMG_ vm_obj_id_t obj) |
| | 104 | { |
| | 105 | vm_meta_entry_t *entry; |
| | 106 | |
| | 107 | /* |
| | 108 | * we can only be an instance of the object if the object is an |
| | 109 | * IntrinsicClass instance, since by default we have only intrinsic |
| | 110 | * classes among our superclasses |
| | 111 | */ |
| | 112 | if (!CVmObjClass::is_intcls_obj(vmg_ obj)) |
| | 113 | return FALSE; |
| | 114 | |
| | 115 | /* |
| | 116 | * look up my metaclass in the metaclass dependency table, and |
| | 117 | * determine if my dependency table entry's record of the |
| | 118 | * IntrinsicClass object for the metaclass matches the given object |
| | 119 | */ |
| | 120 | entry = (G_meta_table |
| | 121 | ->get_entry_from_reg(get_metaclass_reg()->get_reg_idx())); |
| | 122 | |
| | 123 | /* |
| | 124 | * if we have an entry, ask our superclass object if it is an |
| | 125 | * instance of the given object; otherwise, we must not be an |
| | 126 | * instance |
| | 127 | */ |
| | 128 | if (entry != 0) |
| | 129 | { |
| | 130 | /* if this is our direct superclass, we're an instance */ |
| | 131 | if (entry->class_obj_ == obj) |
| | 132 | return TRUE; |
| | 133 | |
| | 134 | /* if there's no intrinsic class object, return false */ |
| | 135 | if (entry->class_obj_ == VM_INVALID_OBJ) |
| | 136 | return FALSE; |
| | 137 | |
| | 138 | /* ask the superclass if it inherits from the given object */ |
| | 139 | return vm_objp(vmg_ entry->class_obj_)->is_instance_of(vmg_ obj); |
| | 140 | } |
| | 141 | else |
| | 142 | { |
| | 143 | /* |
| | 144 | * no metaclass table entry - we can't really make any |
| | 145 | * determination, so indicate that we're not an instance |
| | 146 | */ |
| | 147 | return FALSE; |
| | 148 | } |
| | 149 | } |
| | 150 | |
| | 151 | /* |
| | 152 | * Get the nth superclass. By default, an object's superclass is |
| | 153 | * represented by the intrinsic class object for the metaclass. |
| | 154 | */ |
| | 155 | vm_obj_id_t CVmObject::get_superclass(VMG_ vm_obj_id_t /*self*/, |
| | 156 | int sc_idx) const |
| | 157 | { |
| | 158 | vm_meta_entry_t *entry; |
| | 159 | |
| | 160 | /* we only have one superclass */ |
| | 161 | if (sc_idx != 0) |
| | 162 | return VM_INVALID_OBJ; |
| | 163 | |
| | 164 | /* look up the metaclass entry */ |
| | 165 | entry = (G_meta_table |
| | 166 | ->get_entry_from_reg(get_metaclass_reg()->get_reg_idx())); |
| | 167 | |
| | 168 | /* return the IntrinsicClass object that represents this metaclass */ |
| | 169 | return (entry != 0 ? entry->class_obj_ : VM_INVALID_OBJ); |
| | 170 | } |
| | 171 | |
| | 172 | /* |
| | 173 | * Get a property |
| | 174 | */ |
| | 175 | int CVmObject::get_prop(VMG_ vm_prop_id_t prop, vm_val_t *retval, |
| | 176 | vm_obj_id_t self, vm_obj_id_t *source_obj, uint *argc) |
| | 177 | { |
| | 178 | uint func_idx; |
| | 179 | |
| | 180 | /* translate the property index to an index into our function table */ |
| | 181 | func_idx = G_meta_table |
| | 182 | ->prop_to_vector_idx(metaclass_reg_->get_reg_idx(), prop); |
| | 183 | |
| | 184 | /* if we find it, the source object is the 'Object' intrinsic class */ |
| | 185 | *source_obj = metaclass_reg_->get_class_obj(vmg0_); |
| | 186 | |
| | 187 | /* call the appropriate function */ |
| | 188 | return (this->*func_table_[func_idx])(vmg_ self, retval, argc, |
| | 189 | prop, source_obj); |
| | 190 | } |
| | 191 | |
| | 192 | /* |
| | 193 | * Inherit a property |
| | 194 | */ |
| | 195 | int CVmObject::inh_prop(VMG_ vm_prop_id_t prop, vm_val_t *retval, |
| | 196 | vm_obj_id_t self, vm_obj_id_t orig_target_obj, |
| | 197 | vm_obj_id_t defining_obj, vm_obj_id_t *source_obj, |
| | 198 | uint *argc) |
| | 199 | { |
| | 200 | uint func_idx; |
| | 201 | |
| | 202 | /* |
| | 203 | * We're inheriting. This is never called from native code, as native |
| | 204 | * code does its inheriting directly through C++ calls to base class |
| | 205 | * native code; hence, we can only be called from a byte-code modifier |
| | 206 | * object. |
| | 207 | * |
| | 208 | * First, try looking for a native implementation. We can reach this |
| | 209 | * point if a byte-code object overrides an intrinsic method, then |
| | 210 | * inherits from the byte-code override. |
| | 211 | */ |
| | 212 | func_idx = G_meta_table |
| | 213 | ->prop_to_vector_idx(metaclass_reg_->get_reg_idx(), prop); |
| | 214 | if (func_idx != 0) |
| | 215 | { |
| | 216 | /* the source object is the 'Object' intrinsic class */ |
| | 217 | *source_obj = metaclass_reg_->get_class_obj(vmg0_); |
| | 218 | |
| | 219 | /* call the native implementation */ |
| | 220 | return (this->*func_table_[func_idx])(vmg_ self, retval, argc, |
| | 221 | prop, source_obj); |
| | 222 | } |
| | 223 | |
| | 224 | /* |
| | 225 | * We didn't find it among the intrinsic methods, so look at the |
| | 226 | * modifier objects. |
| | 227 | */ |
| | 228 | return find_modifier_prop(vmg_ prop, retval, self, orig_target_obj, |
| | 229 | defining_obj, source_obj, argc); |
| | 230 | } |
| | 231 | |
| | 232 | /* |
| | 233 | * Get a property that isn't defined in our property table |
| | 234 | */ |
| | 235 | int CVmObject::getp_undef(VMG_ vm_obj_id_t self, |
| | 236 | vm_val_t *retval, uint *argc, |
| | 237 | vm_prop_id_t prop, vm_obj_id_t *source_obj) |
| | 238 | { |
| | 239 | /* |
| | 240 | * We didn't find a native implementation of the method, but there's |
| | 241 | * still one more place to look: the "modifier" object for our class |
| | 242 | * tree. Modifier objects are byte-code objects that can provide |
| | 243 | * implementations of methods that add to intrinsic classes (modifiers |
| | 244 | * can't override intrinsic methods, but they can add new methods). |
| | 245 | * |
| | 246 | * Since we're looking for a property on an initial get-property call |
| | 247 | * (not an inheritance call), we don't yet have a defining object to |
| | 248 | * find and skip in the inheritance tree, so use VM_INVALID_OBJ as the |
| | 249 | * defining object. In addition, we're directly calling the method, so |
| | 250 | * the target object is the same as the 'self' object. |
| | 251 | */ |
| | 252 | return find_modifier_prop(vmg_ prop, retval, self, self, |
| | 253 | VM_INVALID_OBJ, source_obj, argc); |
| | 254 | } |
| | 255 | |
| | 256 | /* |
| | 257 | * Find a modifier property. |
| | 258 | */ |
| | 259 | int CVmObject::find_modifier_prop(VMG_ vm_prop_id_t prop, vm_val_t *retval, |
| | 260 | vm_obj_id_t self, |
| | 261 | vm_obj_id_t orig_target_obj, |
| | 262 | vm_obj_id_t defining_obj, |
| | 263 | vm_obj_id_t *source_obj, |
| | 264 | uint *argc) |
| | 265 | { |
| | 266 | vm_meta_entry_t *entry; |
| | 267 | int found_def_obj; |
| | 268 | |
| | 269 | /* we haven't yet found the defining superclass */ |
| | 270 | found_def_obj = FALSE; |
| | 271 | |
| | 272 | /* get my metaclass from the dependency table */ |
| | 273 | entry = (G_meta_table |
| | 274 | ->get_entry_from_reg(get_metaclass_reg()->get_reg_idx())); |
| | 275 | |
| | 276 | /* |
| | 277 | * if there's an associated intrinsic class object, check to see if |
| | 278 | * it provides a user modifier object for this intrinsic class |
| | 279 | */ |
| | 280 | while (entry != 0 && entry->class_obj_ != VM_INVALID_OBJ) |
| | 281 | { |
| | 282 | vm_obj_id_t mod_obj; |
| | 283 | |
| | 284 | /* ask the intrinsic class object for the user modifier object */ |
| | 285 | mod_obj = ((CVmObjClass *)vm_objp(vmg_ entry->class_obj_)) |
| | 286 | ->get_mod_obj(); |
| | 287 | |
| | 288 | /* |
| | 289 | * If we have a defining object, we must ignore objects in the |
| | 290 | * superclass tree until we find the defining object. Therefore, |
| | 291 | * scan up the superclass tree for mod_obj and see if we can find |
| | 292 | * the defining object; when we find it, we can start looking at |
| | 293 | * objects for real at the defining object's superclass. |
| | 294 | * |
| | 295 | * (Superclasses in modifier objects aren't real superclasses, |
| | 296 | * because modifier objects are classless. Instead, the superclass |
| | 297 | * list simply implements the 'modify' chain.) |
| | 298 | */ |
| | 299 | if (mod_obj != VM_INVALID_OBJ |
| | 300 | && defining_obj != VM_INVALID_OBJ && !found_def_obj) |
| | 301 | { |
| | 302 | /* |
| | 303 | * if the defining object isn't among the byte-code |
| | 304 | * superclasses of the modifier object, we must skip this |
| | 305 | * entire intrinsic class and move to the intrinsic superclass |
| | 306 | */ |
| | 307 | if (mod_obj == defining_obj |
| | 308 | || vm_objp(vmg_ mod_obj)->is_instance_of(vmg_ defining_obj)) |
| | 309 | { |
| | 310 | /* |
| | 311 | * the defining object is among my modifier family - this |
| | 312 | * means that this is the intrinsic superclass where we |
| | 313 | * found the modifier method |
| | 314 | */ |
| | 315 | found_def_obj = TRUE; |
| | 316 | } |
| | 317 | else |
| | 318 | { |
| | 319 | /* |
| | 320 | * The current defining object is not part of the modifier |
| | 321 | * chain for this intrinsic class, so we've already skipped |
| | 322 | * past this point in the intrinsic superclass tree on past |
| | 323 | * inheritances. Simply move to the next intrinsic class |
| | 324 | * and look at its modifier. |
| | 325 | */ |
| | 326 | goto next_intrinsic_sc; |
| | 327 | } |
| | 328 | } |
| | 329 | |
| | 330 | /* |
| | 331 | * If there's a modifier object, send the property request to it. |
| | 332 | * We are effectively delegating the method call to the modifier |
| | 333 | * object, so we must use the "inherited property" call, not the |
| | 334 | * plain get_prop() call: 'self' is the original self, but the |
| | 335 | * target object is the intrinsic class modifier object. |
| | 336 | */ |
| | 337 | if (mod_obj != VM_INVALID_OBJ |
| | 338 | && vm_objp(vmg_ mod_obj)->inh_prop( |
| | 339 | vmg_ prop, retval, self, mod_obj, defining_obj, |
| | 340 | source_obj, argc)) |
| | 341 | return TRUE; |
| | 342 | |
| | 343 | /* we didn't find it in this object, so look at its super-metaclass */ |
| | 344 | next_intrinsic_sc: |
| | 345 | if (entry->meta_->get_supermeta_reg() != 0) |
| | 346 | { |
| | 347 | /* get the super-metaclass ID */ |
| | 348 | entry = (G_meta_table |
| | 349 | ->get_entry_from_reg(entry->meta_ |
| | 350 | ->get_supermeta_reg() |
| | 351 | ->get_reg_idx())); |
| | 352 | |
| | 353 | /* |
| | 354 | * if we've already found the previous defining intrinsic |
| | 355 | * class, we can forget about the previous defining modifier |
| | 356 | * object now: since we're moving to a new intrinsic |
| | 357 | * superclass, we will have no superclass relation to the |
| | 358 | * previous defining object in the new modifier family, so we |
| | 359 | * can simply use the next definition of the property we find |
| | 360 | */ |
| | 361 | if (found_def_obj) |
| | 362 | defining_obj = VM_INVALID_OBJ; |
| | 363 | } |
| | 364 | else |
| | 365 | { |
| | 366 | /* no super-metaclass - give up */ |
| | 367 | break; |
| | 368 | } |
| | 369 | } |
| | 370 | |
| | 371 | /* we don't have a modifier object, so the property is undefined */ |
| | 372 | return FALSE; |
| | 373 | } |
| | 374 | |
| | 375 | /* |
| | 376 | * property evaluator - ofKind |
| | 377 | */ |
| | 378 | int CVmObject::getp_of_kind(VMG_ vm_obj_id_t self, |
| | 379 | vm_val_t *retval, uint *argc, |
| | 380 | vm_prop_id_t, vm_obj_id_t *) |
| | 381 | { |
| | 382 | vm_val_t sc; |
| | 383 | static CVmNativeCodeDesc desc(1); |
| | 384 | |
| | 385 | /* check arguments */ |
| | 386 | if (get_prop_check_argc(retval, argc, &desc)) |
| | 387 | return TRUE; |
| | 388 | |
| | 389 | /* pop the superclass, and make sure it's an object */ |
| | 390 | G_stk->pop(&sc); |
| | 391 | if (sc.typ != VM_OBJ) |
| | 392 | err_throw(VMERR_OBJ_VAL_REQD); |
| | 393 | |
| | 394 | /* check for an identity test */ |
| | 395 | if (sc.val.obj == self) |
| | 396 | { |
| | 397 | /* x.ofKind(x) == true */ |
| | 398 | retval->set_true(); |
| | 399 | } |
| | 400 | else |
| | 401 | { |
| | 402 | /* check to see if the object is a superclass of ours */ |
| | 403 | retval->set_logical(is_instance_of(vmg_ sc.val.obj)); |
| | 404 | } |
| | 405 | |
| | 406 | /* handled */ |
| | 407 | return TRUE; |
| | 408 | } |
| | 409 | |
| | 410 | /* |
| | 411 | * property evaluator - isClass |
| | 412 | */ |
| | 413 | int CVmObject::getp_is_class(VMG_ vm_obj_id_t self, |
| | 414 | vm_val_t *retval, uint *argc, |
| | 415 | vm_prop_id_t, vm_obj_id_t *) |
| | 416 | { |
| | 417 | static CVmNativeCodeDesc desc(0); |
| | 418 | |
| | 419 | /* check arguments */ |
| | 420 | if (get_prop_check_argc(retval, argc, &desc)) |
| | 421 | return TRUE; |
| | 422 | |
| | 423 | /* indicate whether or not we're a class object */ |
| | 424 | retval->set_logical(is_class_object(vmg_ self)); |
| | 425 | |
| | 426 | /* handled */ |
| | 427 | return TRUE; |
| | 428 | } |
| | 429 | |
| | 430 | /* |
| | 431 | * property evaluator - isTransient |
| | 432 | */ |
| | 433 | int CVmObject::getp_is_transient(VMG_ vm_obj_id_t self, |
| | 434 | vm_val_t *retval, uint *argc, |
| | 435 | vm_prop_id_t, vm_obj_id_t *) |
| | 436 | { |
| | 437 | static CVmNativeCodeDesc desc(0); |
| | 438 | |
| | 439 | /* check arguments */ |
| | 440 | if (get_prop_check_argc(retval, argc, &desc)) |
| | 441 | return TRUE; |
| | 442 | |
| | 443 | /* indicate whether or not we're transient */ |
| | 444 | retval->set_logical(G_obj_table->is_obj_transient(self)); |
| | 445 | |
| | 446 | /* handled */ |
| | 447 | return TRUE; |
| | 448 | } |
| | 449 | |
| | 450 | /* |
| | 451 | * property evaluator - getSuperclassList |
| | 452 | */ |
| | 453 | int CVmObject::getp_sclist(VMG_ vm_obj_id_t self, |
| | 454 | vm_val_t *retval, uint *argc, |
| | 455 | vm_prop_id_t, vm_obj_id_t *) |
| | 456 | { |
| | 457 | size_t sc_cnt; |
| | 458 | vm_obj_id_t lst_obj; |
| | 459 | CVmObjList *lstp; |
| | 460 | size_t i; |
| | 461 | static CVmNativeCodeDesc desc(0); |
| | 462 | |
| | 463 | /* check arguments */ |
| | 464 | if (get_prop_check_argc(retval, argc, &desc)) |
| | 465 | return TRUE; |
| | 466 | |
| | 467 | /* push a self-reference for GC protection */ |
| | 468 | G_interpreter->push_obj(vmg_ self); |
| | 469 | |
| | 470 | /* get the number of superclasses */ |
| | 471 | sc_cnt = get_superclass_count(vmg_ self); |
| | 472 | |
| | 473 | /* allocate a list for the results */ |
| | 474 | lst_obj = CVmObjList::create(vmg_ FALSE, sc_cnt); |
| | 475 | lstp = (CVmObjList *)vm_objp(vmg_ lst_obj); |
| | 476 | |
| | 477 | /* build the superclass list */ |
| | 478 | for (i = 0 ; i < sc_cnt ; ++i) |
| | 479 | { |
| | 480 | vm_val_t ele_val; |
| | 481 | |
| | 482 | /* get this superclass */ |
| | 483 | ele_val.set_obj(get_superclass(vmg_ self, i)); |
| | 484 | |
| | 485 | /* set the list element */ |
| | 486 | lstp->cons_set_element(i, &ele_val); |
| | 487 | } |
| | 488 | |
| | 489 | /* discard our GC protection */ |
| | 490 | G_stk->discard(); |
| | 491 | |
| | 492 | /* return the list */ |
| | 493 | retval->set_obj(lst_obj); |
| | 494 | |
| | 495 | /* handled */ |
| | 496 | return TRUE; |
| | 497 | } |
| | 498 | |
| | 499 | /* |
| | 500 | * property evaluator - propDefined |
| | 501 | */ |
| | 502 | int CVmObject::getp_propdef(VMG_ vm_obj_id_t self, |
| | 503 | vm_val_t *retval, uint *in_argc, |
| | 504 | vm_prop_id_t, vm_obj_id_t *) |
| | 505 | { |
| | 506 | uint argc = (in_argc != 0 ? *in_argc : 0); |
| | 507 | vm_val_t val; |
| | 508 | int flags; |
| | 509 | int found; |
| | 510 | vm_prop_id_t prop; |
| | 511 | vm_obj_id_t source_obj; |
| | 512 | static CVmNativeCodeDesc desc(1, 1); |
| | 513 | |
| | 514 | /* check arguments */ |
| | 515 | if (get_prop_check_argc(retval, in_argc, &desc)) |
| | 516 | return TRUE; |
| | 517 | |
| | 518 | /* pop the property address to test */ |
| | 519 | G_interpreter->pop_prop(vmg_ &val); |
| | 520 | prop = val.val.prop; |
| | 521 | |
| | 522 | /* if we have the flag argument, get it; otherwise, use the default */ |
| | 523 | if (argc >= 2) |
| | 524 | { |
| | 525 | /* get the flag value */ |
| | 526 | G_interpreter->pop_int(vmg_ &val); |
| | 527 | flags = (int)val.val.intval; |
| | 528 | } |
| | 529 | else |
| | 530 | { |
| | 531 | /* use the default flags */ |
| | 532 | flags = VMOBJ_PROPDEF_ANY; |
| | 533 | } |
| | 534 | |
| | 535 | /* presume we won't find a valid source object */ |
| | 536 | source_obj = VM_INVALID_OBJ; |
| | 537 | |
| | 538 | /* look up the property */ |
| | 539 | found = get_prop(vmg_ prop, &val, self, &source_obj, 0); |
| | 540 | |
| | 541 | /* |
| | 542 | * If we found a result, check to see if it's an intrinsic class |
| | 543 | * modifier object. If it is, replace it with its intrinsic class: |
| | 544 | * modifier objects are invisible through the reflection mechanism, and |
| | 545 | * appear to be the actual intrinsic classes they modify. |
| | 546 | */ |
| | 547 | if (found && CVmObjIntClsMod::is_intcls_mod_obj(vmg_ source_obj)) |
| | 548 | source_obj = find_intcls_for_mod(vmg_ self, source_obj); |
| | 549 | |
| | 550 | /* check the flags */ |
| | 551 | switch(flags) |
| | 552 | { |
| | 553 | case VMOBJ_PROPDEF_ANY: |
| | 554 | /* return true if the property is defined */ |
| | 555 | retval->set_logical(found); |
| | 556 | break; |
| | 557 | |
| | 558 | case VMOBJ_PROPDEF_DIRECTLY: |
| | 559 | /* return true if the property is defined directly */ |
| | 560 | retval->set_logical(found && source_obj == self); |
| | 561 | break; |
| | 562 | |
| | 563 | case VMOBJ_PROPDEF_INHERITS: |
| | 564 | /* return true if the property is inherited only */ |
| | 565 | retval->set_logical(found && source_obj != self); |
| | 566 | break; |
| | 567 | |
| | 568 | case VMOBJ_PROPDEF_GET_CLASS: |
| | 569 | /* return the defining class, or nil if it's not defined */ |
| | 570 | if (found) |
| | 571 | { |
| | 572 | /* |
| | 573 | * If we got a valid source object, return it. If we didn't |
| | 574 | * get a valid source object, but we found the property, |
| | 575 | * return 'self' as the result; this isn't exactly right, but |
| | 576 | * this should only be possible when the source object is an |
| | 577 | * intrinsic class for which no intrinsic class object is |
| | 578 | * defined, in which case the best we can do is provide 'self' |
| | 579 | * as the answer. |
| | 580 | */ |
| | 581 | retval->set_obj(source_obj != VM_INVALID_OBJ ? source_obj : self); |
| | 582 | } |
| | 583 | else |
| | 584 | { |
| | 585 | /* didn't find it - the return value is nil */ |
| | 586 | retval->set_nil(); |
| | 587 | } |
| | 588 | break; |
| | 589 | |
| | 590 | default: |
| | 591 | /* other flags are invalid */ |
| | 592 | err_throw(VMERR_BAD_VAL_BIF); |
| | 593 | break; |
| | 594 | } |
| | 595 | |
| | 596 | /* handled */ |
| | 597 | return TRUE; |
| | 598 | } |
| | 599 | |
| | 600 | /* |
| | 601 | * Find the intrinsic class which the given modifier object modifies. This |
| | 602 | * can only be used with a modifier that modifies my intrinsic class or one |
| | 603 | * of its intrinsic superclasses. |
| | 604 | */ |
| | 605 | vm_obj_id_t CVmObject::find_intcls_for_mod(VMG_ vm_obj_id_t self, |
| | 606 | vm_obj_id_t mod_obj) |
| | 607 | { |
| | 608 | vm_meta_entry_t *entry; |
| | 609 | |
| | 610 | /* get my metaclass from the dependency table */ |
| | 611 | entry = (G_meta_table |
| | 612 | ->get_entry_from_reg(get_metaclass_reg()->get_reg_idx())); |
| | 613 | |
| | 614 | /* |
| | 615 | * if there's an intrinsic class object for the metaclass, ask it to do |
| | 616 | * the work |
| | 617 | */ |
| | 618 | if (entry != 0 && entry->class_obj_ != VM_INVALID_OBJ) |
| | 619 | return (((CVmObjClass *)vm_objp(vmg_ entry->class_obj_)) |
| | 620 | ->find_mod_src_obj(vmg_ entry->class_obj_, mod_obj)); |
| | 621 | |
| | 622 | /* there's no intrinsic metaclass, so we can't find what we need */ |
| | 623 | return VM_INVALID_OBJ; |
| | 624 | } |
| | 625 | |
| | 626 | /* |
| | 627 | * property evaluator - propInherited |
| | 628 | */ |
| | 629 | int CVmObject::getp_propinh(VMG_ vm_obj_id_t self, |
| | 630 | vm_val_t *retval, uint *in_argc, |
| | 631 | vm_prop_id_t, vm_obj_id_t *) |
| | 632 | { |
| | 633 | uint argc = (in_argc != 0 ? *in_argc : 0); |
| | 634 | vm_val_t val; |
| | 635 | int flags; |
| | 636 | int found; |
| | 637 | vm_prop_id_t prop; |
| | 638 | vm_obj_id_t source_obj; |
| | 639 | vm_obj_id_t orig_target_obj; |
| | 640 | vm_obj_id_t defining_obj; |
| | 641 | static CVmNativeCodeDesc desc(3, 1); |
| | 642 | |
| | 643 | /* check arguments */ |
| | 644 | if (get_prop_check_argc(retval, in_argc, &desc)) |
| | 645 | return TRUE; |
| | 646 | |
| | 647 | /* pop the property address to test */ |
| | 648 | G_interpreter->pop_prop(vmg_ &val); |
| | 649 | prop = val.val.prop; |
| | 650 | |
| | 651 | /* get the original target object */ |
| | 652 | G_interpreter->pop_obj(vmg_ &val); |
| | 653 | orig_target_obj = val.val.obj; |
| | 654 | |
| | 655 | /* get the defining object */ |
| | 656 | G_interpreter->pop_obj(vmg_ &val); |
| | 657 | defining_obj = val.val.obj; |
| | 658 | |
| | 659 | /* if we have the flag argument, get it; otherwise, use the default */ |
| | 660 | if (argc >= 4) |
| | 661 | { |
| | 662 | /* get the flag value */ |
| | 663 | G_interpreter->pop_int(vmg_ &val); |
| | 664 | flags = (int)val.val.intval; |
| | 665 | } |
| | 666 | else |
| | 667 | { |
| | 668 | /* use the default flags */ |
| | 669 | flags = VMOBJ_PROPDEF_ANY; |
| | 670 | } |
| | 671 | |
| | 672 | /* presume we won't find a valid source object */ |
| | 673 | source_obj = VM_INVALID_OBJ; |
| | 674 | |
| | 675 | /* look up the property */ |
| | 676 | found = inh_prop(vmg_ prop, &val, self, orig_target_obj, defining_obj, |
| | 677 | &source_obj, 0); |
| | 678 | |
| | 679 | /* check the flags */ |
| | 680 | switch(flags) |
| | 681 | { |
| | 682 | case VMOBJ_PROPDEF_ANY: |
| | 683 | /* return true if the property is defined */ |
| | 684 | retval->set_logical(found); |
| | 685 | break; |
| | 686 | |
| | 687 | case VMOBJ_PROPDEF_GET_CLASS: |
| | 688 | /* return the defining class, or nil if it's not defined */ |
| | 689 | if (found) |
| | 690 | { |
| | 691 | /* return the source object, or 'self' if we didn't find one */ |
| | 692 | retval->set_obj(source_obj != VM_INVALID_OBJ ? source_obj : self); |
| | 693 | } |
| | 694 | else |
| | 695 | { |
| | 696 | /* didn't find it - the return value is nil */ |
| | 697 | retval->set_nil(); |
| | 698 | } |
| | 699 | break; |
| | 700 | |
| | 701 | default: |
| | 702 | /* other flags are invalid */ |
| | 703 | err_throw(VMERR_BAD_VAL_BIF); |
| | 704 | break; |
| | 705 | } |
| | 706 | |
| | 707 | /* handled */ |
| | 708 | return TRUE; |
| | 709 | } |
| | 710 | |
| | 711 | /* |
| | 712 | * property evaluator - propType |
| | 713 | */ |
| | 714 | int CVmObject::getp_proptype(VMG_ vm_obj_id_t self, |
| | 715 | vm_val_t *retval, uint *argc, |
| | 716 | vm_prop_id_t, vm_obj_id_t *) |
| | 717 | { |
| | 718 | vm_val_t val; |
| | 719 | vm_prop_id_t prop; |
| | 720 | vm_obj_id_t source_obj; |
| | 721 | static CVmNativeCodeDesc desc(1); |
| | 722 | |
| | 723 | /* check arguments */ |
| | 724 | if (get_prop_check_argc(retval, argc, &desc)) |
| | 725 | return TRUE; |
| | 726 | |
| | 727 | /* pop the property address to test */ |
| | 728 | G_interpreter->pop_prop(vmg_ &val); |
| | 729 | prop = val.val.prop; |
| | 730 | |
| | 731 | /* get the property value */ |
| | 732 | if (!get_prop(vmg_ prop, &val, self, &source_obj, 0)) |
| | 733 | { |
| | 734 | /* the property isn't defined on the object - the result is nil */ |
| | 735 | retval->set_nil(); |
| | 736 | } |
| | 737 | else |
| | 738 | { |
| | 739 | /* set the return value to the property's datatype value */ |
| | 740 | retval->set_datatype(vmg_ &val); |
| | 741 | } |
| | 742 | |
| | 743 | /* handled */ |
| | 744 | return TRUE; |
| | 745 | } |
| | 746 | |
| | 747 | /* |
| | 748 | * property evaluator - getPropList |
| | 749 | */ |
| | 750 | int CVmObject::getp_get_prop_list(VMG_ vm_obj_id_t self, |
| | 751 | vm_val_t *retval, uint *argc, |
| | 752 | vm_prop_id_t, vm_obj_id_t *) |
| | 753 | { |
| | 754 | static CVmNativeCodeDesc desc(0); |
| | 755 | |
| | 756 | /* check arguments */ |
| | 757 | if (get_prop_check_argc(retval, argc, &desc)) |
| | 758 | return TRUE; |
| | 759 | |
| | 760 | /* push a self-reference for gc protection */ |
| | 761 | G_stk->push()->set_obj(self); |
| | 762 | |
| | 763 | /* build my property list */ |
| | 764 | build_prop_list(vmg_ self, retval); |
| | 765 | |
| | 766 | /* discard the gc protection */ |
| | 767 | G_stk->discard(); |
| | 768 | |
| | 769 | /* handled */ |
| | 770 | return TRUE; |
| | 771 | } |
| | 772 | |
| | 773 | /* |
| | 774 | * Build a list of properties directly defined by this instance |
| | 775 | */ |
| | 776 | void CVmObject::build_prop_list(VMG_ vm_obj_id_t /*self*/, vm_val_t *retval) |
| | 777 | { |
| | 778 | /* |
| | 779 | * by default, object instances have no directly defined properties, |
| | 780 | * so create and return an empty list |
| | 781 | */ |
| | 782 | retval->set_obj(CVmObjList::create(vmg_ FALSE, (size_t)0)); |
| | 783 | } |
| | 784 | |
| | 785 | /* |
| | 786 | * property evaluator - getPropParams |
| | 787 | */ |
| | 788 | int CVmObject::getp_get_prop_params(VMG_ vm_obj_id_t self, |
| | 789 | vm_val_t *retval, uint *argc, |
| | 790 | vm_prop_id_t, vm_obj_id_t *) |
| | 791 | { |
| | 792 | vm_val_t val; |
| | 793 | vm_prop_id_t prop; |
| | 794 | vm_obj_id_t source_obj; |
| | 795 | int min_args, opt_args, varargs; |
| | 796 | static CVmNativeCodeDesc desc(1); |
| | 797 | CVmObjList *lst; |
| | 798 | |
| | 799 | /* check arguments */ |
| | 800 | if (get_prop_check_argc(retval, argc, &desc)) |
| | 801 | return TRUE; |
| | 802 | |
| | 803 | /* pop the property address to test */ |
| | 804 | G_interpreter->pop_prop(vmg_ &val); |
| | 805 | prop = val.val.prop; |
| | 806 | |
| | 807 | /* push a self-reference while we're working */ |
| | 808 | G_stk->push()->set_obj(self); |
| | 809 | |
| | 810 | /* get the property value */ |
| | 811 | if (!get_prop(vmg_ prop, &val, self, &source_obj, 0)) |
| | 812 | { |
| | 813 | /* no such method - no arguments */ |
| | 814 | min_args = opt_args = 0; |
| | 815 | varargs = FALSE; |
| | 816 | } |
| | 817 | else if (val.typ == VM_CODEOFS) |
| | 818 | { |
| | 819 | CVmFuncPtr func; |
| | 820 | |
| | 821 | /* get the function header for the code offset */ |
| | 822 | func.set((const uchar *)G_code_pool->get_ptr(val.val.ofs)); |
| | 823 | |
| | 824 | /* |
| | 825 | * Get the argument information from the function header. Note |
| | 826 | * that p-code methods cannot have optional arguments. |
| | 827 | */ |
| | 828 | min_args = func.get_min_argc(); |
| | 829 | opt_args = 0; |
| | 830 | varargs = func.is_varargs(); |
| | 831 | |
| | 832 | /* |
| | 833 | * re-evaluate the currently executing method's entry pointer, to |
| | 834 | * ensure that the current code page is the most recently used |
| | 835 | * page, in case we're on a swapping system (note that we don't |
| | 836 | * attempt to retranslate the pointer, since we assume that we |
| | 837 | * didn't actually swap it out just now - we assume that we have |
| | 838 | * enough cache space to keep at least two pages in memory at |
| | 839 | * once, and since the currently-executing code page should have |
| | 840 | * been the most recently used page before our translation just |
| | 841 | * above, it should not have been swapped out) |
| | 842 | */ |
| | 843 | G_interpreter->touch_entry_ptr_page(vmg0_); |
| | 844 | } |
| | 845 | else if (val.typ == VM_NATIVE_CODE) |
| | 846 | { |
| | 847 | /* get the arguments from the native code descriptor */ |
| | 848 | min_args = val.val.native_desc->min_argc_; |
| | 849 | opt_args = val.val.native_desc->opt_argc_; |
| | 850 | varargs = val.val.native_desc->varargs_; |
| | 851 | } |
| | 852 | else |
| | 853 | { |
| | 854 | /* it's not a function - no arguments */ |
| | 855 | min_args = opt_args = 0; |
| | 856 | varargs = FALSE; |
| | 857 | } |
| | 858 | |
| | 859 | /* |
| | 860 | * Allocate our return list. We need three elements: [minArgs, |
| | 861 | * optionalArgs, isVarargs]. |
| | 862 | */ |
| | 863 | retval->set_obj(CVmObjList::create(vmg_ FALSE, 3)); |
| | 864 | |
| | 865 | /* get the list object, properly cast */ |
| | 866 | lst = (CVmObjList *)vm_objp(vmg_ retval->val.obj); |
| | 867 | |
| | 868 | /* set the minimum argument count */ |
| | 869 | val.set_int(min_args); |
| | 870 | lst->cons_set_element(0, &val); |
| | 871 | |
| | 872 | /* set the optional argument count */ |
| | 873 | val.set_int(opt_args); |
| | 874 | lst->cons_set_element(1, &val); |
| | 875 | |
| | 876 | /* set the varargs flag */ |
| | 877 | val.set_logical(varargs); |
| | 878 | lst->cons_set_element(2, &val); |
| | 879 | |
| | 880 | /* discard our self-reference */ |
| | 881 | G_stk->discard(); |
| | 882 | |
| | 883 | /* handled */ |
| | 884 | return TRUE; |
| | 885 | } |
| | 886 | |
| | 887 | /* |
| | 888 | * Call a static property |
| | 889 | */ |
| | 890 | int CVmObject::call_stat_prop(VMG_ vm_val_t *retval, const uchar **pc_ptr, |
| | 891 | uint *argc, vm_prop_id_t prop) |
| | 892 | { |
| | 893 | /* not handled */ |
| | 894 | return FALSE; |
| | 895 | } |
| | 896 | |
| | 897 | |
| | 898 | /* ------------------------------------------------------------------------ */ |
| | 899 | /* |
| | 900 | * CVmMetaclass implementation |
| | 901 | */ |
| | 902 | |
| | 903 | /* |
| | 904 | * Get a metaclass's super-metaclass. We'll look up our super-metaclass |
| | 905 | * in the metaclass registration table and return the IntrinsicClass |
| | 906 | * object we find referenced there. |
| | 907 | */ |
| | 908 | vm_obj_id_t CVmMetaclass::get_supermeta(VMG_ int idx) const |
| | 909 | { |
| | 910 | vm_meta_entry_t *entry; |
| | 911 | |
| | 912 | /* we only have one supermetaclass */ |
| | 913 | if (idx != 0) |
| | 914 | return VM_INVALID_OBJ; |
| | 915 | |
| | 916 | /* if I don't have a supermetaclass at all, return nil */ |
| | 917 | if (get_supermeta_reg() == 0) |
| | 918 | return VM_INVALID_OBJ; |
| | 919 | |
| | 920 | /* look up my supermetaclass entry */ |
| | 921 | entry = (G_meta_table->get_entry_from_reg( |
| | 922 | get_supermeta_reg()->get_reg_idx())); |
| | 923 | |
| | 924 | /* return the IntrinsicClass object that represents this metaclass */ |
| | 925 | return (entry != 0 ? entry->class_obj_ : VM_INVALID_OBJ); |
| | 926 | } |
| | 927 | |
| | 928 | /* |
| | 929 | * Determine if I'm an instance of the given object. Most metaclasses |
| | 930 | * inherit directly from CVmObject, so we'll return true only if the |
| | 931 | * object is the CVmObject IntrinsicClass object |
| | 932 | */ |
| | 933 | int CVmMetaclass::is_meta_instance_of(VMG_ vm_obj_id_t obj) const |
| | 934 | { |
| | 935 | vm_meta_entry_t *entry; |
| | 936 | CVmMetaclass *sc; |
| | 937 | |
| | 938 | /* iterate over my supermetaclasses */ |
| | 939 | for (sc = get_supermeta_reg() ; sc != 0 ; sc = sc->get_supermeta_reg()) |
| | 940 | { |
| | 941 | /* look up the metaclass entry for this supermetaclass */ |
| | 942 | entry = (G_meta_table->get_entry_from_reg(sc->get_reg_idx())); |
| | 943 | |
| | 944 | /* |
| | 945 | * if the object matches the current superclass's IntrinsicClass |
| | 946 | * object, we're a subclass of that object; otherwise we're not |
| | 947 | */ |
| | 948 | if (entry != 0 && entry->class_obj_ == obj) |
| | 949 | return TRUE; |
| | 950 | } |
| | 951 | |
| | 952 | /* it's not one of my superclasses */ |
| | 953 | return FALSE; |
| | 954 | } |
| | 955 | |
| | 956 | /* |
| | 957 | * Get my intrinsic class object |
| | 958 | */ |
| | 959 | vm_obj_id_t CVmMetaclass::get_class_obj(VMG0_) const |
| | 960 | { |
| | 961 | vm_meta_entry_t *entry; |
| | 962 | |
| | 963 | /* get my metacalss entry */ |
| | 964 | entry = G_meta_table->get_entry_from_reg(get_reg_idx()); |
| | 965 | |
| | 966 | /* if we found our entry, return the class from the entry */ |
| | 967 | return (entry != 0 ? entry->class_obj_ : VM_INVALID_OBJ); |
| | 968 | } |
| | 969 | |
| | 970 | /* ------------------------------------------------------------------------ */ |
| | 971 | /* |
| | 972 | * object table implementation |
| | 973 | */ |
| | 974 | |
| | 975 | /* |
| | 976 | * allocate object table |
| | 977 | */ |
| | 978 | void CVmObjTable::init() |
| | 979 | { |
| | 980 | /* allocate the initial set of page slots */ |
| | 981 | page_slots_ = 10; |
| | 982 | pages_ = (CVmObjPageEntry **)t3malloc(page_slots_ * sizeof(*pages_)); |
| | 983 | |
| | 984 | /* if that failed, throw an error */ |
| | 985 | if (pages_ == 0) |
| | 986 | err_throw(VMERR_OUT_OF_MEMORY); |
| | 987 | |
| | 988 | /* no pages are in use yet */ |
| | 989 | pages_used_ = 0; |
| | 990 | |
| | 991 | /* there are no free objects yet */ |
| | 992 | first_free_ = 0; |
| | 993 | |
| | 994 | /* there's nothing in the GC work queue yet */ |
| | 995 | gc_queue_head_ = VM_INVALID_OBJ; |
| | 996 | |
| | 997 | /* nothing in the finalizer work queue yet */ |
| | 998 | finalize_queue_head_ = VM_INVALID_OBJ; |
| | 999 | |
| | 1000 | /* we haven't allocated anything yet */ |
| | 1001 | allocs_since_gc_ = 0; |
| | 1002 | |
| | 1003 | /* |
| | 1004 | * Set the upper limit for allocations between garbage collection. |
| | 1005 | * We want to choose this number so that we balance the time it |
| | 1006 | * takes to collect garbage against the memory it consumes to leave |
| | 1007 | * it uncollected. |
| | 1008 | */ |
| | 1009 | max_allocs_between_gc_ = 1000; |
| | 1010 | |
| | 1011 | /* enable the garbage collector */ |
| | 1012 | gc_enabled_ = TRUE; |
| | 1013 | |
| | 1014 | /* there are no saved image data pointers yet */ |
| | 1015 | image_ptr_head_ = 0; |
| | 1016 | image_ptr_tail_ = 0; |
| | 1017 | image_ptr_last_cnt_ = 0; |
| | 1018 | |
| | 1019 | /* no global pages yet */ |
| | 1020 | globals_ = 0; |
| | 1021 | |
| | 1022 | /* no global variables yet */ |
| | 1023 | global_var_head_ = 0; |
| | 1024 | |
| | 1025 | /* create the post_load_init() request table */ |
| | 1026 | post_load_init_table_ = new CVmHashTable(128, new CVmHashFuncCS(), TRUE); |
| | 1027 | } |
| | 1028 | |
| | 1029 | /* |
| | 1030 | * delete object table |
| | 1031 | */ |
| | 1032 | CVmObjTable::~CVmObjTable() |
| | 1033 | { |
| | 1034 | } |
| | 1035 | |
| | 1036 | /* |
| | 1037 | * Delete the table. (We need to separate this out into a method so |
| | 1038 | * that we can get access to the global variables.) |
| | 1039 | */ |
| | 1040 | void CVmObjTable::delete_obj_table(VMG0_) |
| | 1041 | { |
| | 1042 | size_t i; |
| | 1043 | vm_image_ptr_page *ip_page; |
| | 1044 | vm_image_ptr_page *ip_next; |
| | 1045 | |
| | 1046 | /* delete all entries in the post-load initialization table */ |
| | 1047 | post_load_init_table_->delete_all_entries(); |
| | 1048 | |
| | 1049 | /* go through the pages and delete the entries */ |
| | 1050 | for (i = 0 ; i < pages_used_ ; ++i) |
| | 1051 | { |
| | 1052 | int j; |
| | 1053 | CVmObjPageEntry *entry; |
| | 1054 | |
| | 1055 | /* delete all of the objects on the page */ |
| | 1056 | for (j = 0, entry = pages_[i] ; j < VM_OBJ_PAGE_CNT ; ++j, ++entry) |
| | 1057 | { |
| | 1058 | /* if this entry is still in use, delete it */ |
| | 1059 | if (!entry->free_) |
| | 1060 | entry->get_vm_obj()->notify_delete(vmg_ entry->in_root_set_); |
| | 1061 | } |
| | 1062 | } |
| | 1063 | |
| | 1064 | /* delete each page we've allocated */ |
| | 1065 | for (i = 0 ; i < pages_used_ ; ++i) |
| | 1066 | { |
| | 1067 | /* delete this page */ |
| | 1068 | t3free(pages_[i]); |
| | 1069 | } |
| | 1070 | |
| | 1071 | /* free the master page table */ |
| | 1072 | t3free(pages_); |
| | 1073 | |
| | 1074 | /* we no longer have any pages */ |
| | 1075 | pages_ = 0; |
| | 1076 | page_slots_ = 0; |
| | 1077 | pages_used_ = 0; |
| | 1078 | |
| | 1079 | /* delete each object image data pointer page */ |
| | 1080 | for (ip_page = image_ptr_head_ ; ip_page != 0 ; ip_page = ip_next) |
| | 1081 | { |
| | 1082 | /* remember the next page (before we delete this one) */ |
| | 1083 | ip_next = ip_page->next_; |
| | 1084 | |
| | 1085 | /* delete this page */ |
| | 1086 | t3free(ip_page); |
| | 1087 | } |
| | 1088 | |
| | 1089 | /* delete the linked list of globals */ |
| | 1090 | if (globals_ != 0) |
| | 1091 | { |
| | 1092 | delete globals_; |
| | 1093 | globals_ = 0; |
| | 1094 | } |
| | 1095 | |
| | 1096 | /* |
| | 1097 | * delete any left-over global variables (these should always be |
| | 1098 | * deleted by subsystems before we get here, but this is the last |
| | 1099 | * chance, so clean them up manually) |
| | 1100 | */ |
| | 1101 | while (global_var_head_ != 0) |
| | 1102 | delete_global_var(global_var_head_); |
| | 1103 | |
| | 1104 | /* delete the post-load initialization table */ |
| | 1105 | delete post_load_init_table_; |
| | 1106 | post_load_init_table_ = 0; |
| | 1107 | } |
| | 1108 | |
| | 1109 | /* |
| | 1110 | * Enable or disable garbage collection |
| | 1111 | */ |
| | 1112 | int CVmObjTable::enable_gc(VMG_ int enable) |
| | 1113 | { |
| | 1114 | int old_enable; |
| | 1115 | |
| | 1116 | /* remember the old status for returning */ |
| | 1117 | old_enable = gc_enabled_; |
| | 1118 | |
| | 1119 | /* set the new status */ |
| | 1120 | gc_enabled_ = enable; |
| | 1121 | |
| | 1122 | /* |
| | 1123 | * if we're enabling GC after it was previously disabled, check to |
| | 1124 | * see if we should perform a GC pass now (but don't count this as a |
| | 1125 | * separate allocation) |
| | 1126 | */ |
| | 1127 | if (!old_enable && enable) |
| | 1128 | alloc_check_gc(vmg_ FALSE); |
| | 1129 | |
| | 1130 | /* return the previous status */ |
| | 1131 | return old_enable; |
| | 1132 | } |
| | 1133 | |
| | 1134 | /* |
| | 1135 | * allocate a new object ID |
| | 1136 | */ |
| | 1137 | vm_obj_id_t CVmObjTable::alloc_obj(VMG_ int in_root_set, |
| | 1138 | int can_have_refs, int can_have_weak_refs) |
| | 1139 | { |
| | 1140 | vm_obj_id_t ret; |
| | 1141 | CVmObjPageEntry *entry; |
| | 1142 | |
| | 1143 | /* count the allocation and maybe perform garbage collection */ |
| | 1144 | alloc_check_gc(vmg_ TRUE); |
| | 1145 | |
| | 1146 | /* if the free list is empty, allocate a new page of object entries */ |
| | 1147 | if (first_free_ == VM_INVALID_OBJ) |
| | 1148 | alloc_new_page(); |
| | 1149 | |
| | 1150 | /* remember the first item in the free list - this is our result */ |
| | 1151 | ret = first_free_; |
| | 1152 | |
| | 1153 | /* get the object table entry for the ID */ |
| | 1154 | entry = get_entry(ret); |
| | 1155 | |
| | 1156 | /* unlink new entry from the free list */ |
| | 1157 | first_free_ = entry->next_obj_; |
| | 1158 | if (entry->next_obj_ != VM_INVALID_OBJ) |
| | 1159 | get_entry(entry->next_obj_)->ptr_.prev_free_ = entry->ptr_.prev_free_; |
| | 1160 | |
| | 1161 | /* initialize the entry */ |
| | 1162 | init_entry_for_alloc(ret, entry, in_root_set, |
| | 1163 | can_have_refs, can_have_weak_refs); |
| | 1164 | |
| | 1165 | /* return the free object */ |
| | 1166 | return ret; |
| | 1167 | } |
| | 1168 | |
| | 1169 | /* |
| | 1170 | * Run garbage collection before allocating an object |
| | 1171 | */ |
| | 1172 | void CVmObjTable::gc_before_alloc(VMG0_) |
| | 1173 | { |
| | 1174 | /* run a full garbage collection pass */ |
| | 1175 | gc_pass_init(vmg0_); |
| | 1176 | gc_pass_finish(vmg0_); |
| | 1177 | } |
| | 1178 | |
| | 1179 | /* |
| | 1180 | * Allocate an object with a particular ID |
| | 1181 | */ |
| | 1182 | void CVmObjTable::alloc_obj_with_id(vm_obj_id_t id, int in_root_set, |
| | 1183 | int can_have_refs, int can_have_weak_refs) |
| | 1184 | { |
| | 1185 | CVmObjPageEntry *entry; |
| | 1186 | |
| | 1187 | /* |
| | 1188 | * if the page containing the given object ID hasn't been allocated, |
| | 1189 | * allocate pages until it's available |
| | 1190 | */ |
| | 1191 | while (id >= pages_used_ * VM_OBJ_PAGE_CNT) |
| | 1192 | alloc_new_page(); |
| | 1193 | |
| | 1194 | /* get the object table entry for the ID */ |
| | 1195 | entry = get_entry(id); |
| | 1196 | |
| | 1197 | /* if the desired object ID is already taken, it's an error */ |
| | 1198 | if (!entry->free_) |
| | 1199 | err_throw(VMERR_OBJ_IN_USE); |
| | 1200 | |
| | 1201 | /* unlink the item - set the previous item's forward pointer... */ |
| | 1202 | if (entry->ptr_.prev_free_ == VM_INVALID_OBJ) |
| | 1203 | first_free_ = entry->next_obj_; |
| | 1204 | else |
| | 1205 | get_entry(entry->ptr_.prev_free_)->next_obj_ = entry->next_obj_; |
| | 1206 | |
| | 1207 | /* ...and the next items back pointer */ |
| | 1208 | if (entry->next_obj_ != VM_INVALID_OBJ) |
| | 1209 | get_entry(entry->next_obj_)->ptr_.prev_free_ = entry->ptr_.prev_free_; |
| | 1210 | |
| | 1211 | /* initialize the entry for allocation */ |
| | 1212 | init_entry_for_alloc(id, entry, in_root_set, |
| | 1213 | can_have_refs, can_have_weak_refs); |
| | 1214 | } |
| | 1215 | |
| | 1216 | /* |
| | 1217 | * Initialize an object table entry that we've just allocated |
| | 1218 | */ |
| | 1219 | void CVmObjTable::init_entry_for_alloc(vm_obj_id_t id, |
| | 1220 | CVmObjPageEntry *entry, |
| | 1221 | int in_root_set, |
| | 1222 | int can_have_refs, |
| | 1223 | int can_have_weak_refs) |
| | 1224 | { |
| | 1225 | /* mark the entry as in use */ |
| | 1226 | entry->free_ = FALSE; |
| | 1227 | |
| | 1228 | /* no undo savepoint has been created since the object was created */ |
| | 1229 | entry->in_undo_ = FALSE; |
| | 1230 | |
| | 1231 | /* mark the object as being in the root set if appropriate */ |
| | 1232 | entry->in_root_set_ = in_root_set; |
| | 1233 | |
| | 1234 | /* presume it's an ordinary persistent object */ |
| | 1235 | entry->transient_ = FALSE; |
| | 1236 | |
| | 1237 | /* presume it won't need post-load initialization */ |
| | 1238 | entry->requested_post_load_init_ = FALSE; |
| | 1239 | |
| | 1240 | /* set the GC characteristics as requested */ |
| | 1241 | entry->can_have_refs_ = can_have_refs; |
| | 1242 | entry->can_have_weak_refs_ = can_have_weak_refs; |
| | 1243 | |
| | 1244 | /* |
| | 1245 | * Mark the object as initially unreachable and unfinalizable. It's |
| | 1246 | * not necessarily really unreachable at this point, but we mark it |
| | 1247 | * as such because the garbage collector hasn't explicitly traced |
| | 1248 | * the object to be reachable. The initial conditions for garbage |
| | 1249 | * collection are that all objects not in the root set and not |
| | 1250 | * finalizable are marked as unreachable; since we're not in a gc |
| | 1251 | * pass right now (we can't be - memory cannot be allocated during a |
| | 1252 | * gc pass), we know that we must establish initial gc conditions |
| | 1253 | * for the next time we start a gc pass. |
| | 1254 | */ |
| | 1255 | entry->reachable_ = VMOBJ_UNREACHABLE; |
| | 1256 | entry->finalize_state_ = VMOBJ_UNFINALIZABLE; |
| | 1257 | |
| | 1258 | /* add it to the GC work queue for the next GC pass */ |
| | 1259 | if (in_root_set) |
| | 1260 | add_to_gc_queue(id, entry, VMOBJ_REACHABLE); |
| | 1261 | } |
| | 1262 | |
| | 1263 | #if 0 // moved to in-line in header, since it's called *a lot* |
| | 1264 | /* |
| | 1265 | * get the page entry for a given ID |
| | 1266 | */ |
| | 1267 | CVmObjPageEntry *CVmObjTable::get_entry(vm_obj_id_t id) const |
| | 1268 | { |
| | 1269 | size_t main_idx; |
| | 1270 | size_t page_idx; |
| | 1271 | |
| | 1272 | /* get the index of the page in the main array */ |
| | 1273 | main_idx = (size_t)(id >> VM_OBJ_PAGE_CNT_LOG2); |
| | 1274 | |
| | 1275 | /* get the index within the page */ |
| | 1276 | page_idx = (size_t)(id & (VM_OBJ_PAGE_CNT - 1)); |
| | 1277 | |
| | 1278 | /* get the object */ |
| | 1279 | return &pages_[main_idx][page_idx]; |
| | 1280 | } |
| | 1281 | #endif |
| | 1282 | |
| | 1283 | /* |
| | 1284 | * Delete an object, given the object table entry. |
| | 1285 | */ |
| | 1286 | void CVmObjTable::delete_entry(VMG_ vm_obj_id_t id, CVmObjPageEntry *entry) |
| | 1287 | { |
| | 1288 | /* mark the object table entry as free */ |
| | 1289 | entry->free_ = TRUE; |
| | 1290 | |
| | 1291 | /* it's not in the root set if it's free */ |
| | 1292 | entry->in_root_set_ = FALSE; |
| | 1293 | |
| | 1294 | /* |
| | 1295 | * notify the object that it's being deleted - this will let the |
| | 1296 | * object release any additional resources (such as variable-size |
| | 1297 | * heap space) that it's holding |
| | 1298 | */ |
| | 1299 | entry->get_vm_obj()->notify_delete(vmg_ FALSE); |
| | 1300 | |
| | 1301 | /* |
| | 1302 | * remove any post-load initialization request for the object, if it |
| | 1303 | * ever requested post-load initialization |
| | 1304 | */ |
| | 1305 | if (entry->requested_post_load_init_) |
| | 1306 | remove_post_load_init(id); |
| | 1307 | |
| | 1308 | /* link this object into the head of the free list */ |
| | 1309 | entry->next_obj_ = first_free_; |
| | 1310 | |
| | 1311 | /* link the previous head back to this object */ |
| | 1312 | if (first_free_ != VM_INVALID_OBJ) |
| | 1313 | get_entry(first_free_)->ptr_.prev_free_ = id; |
| | 1314 | |
| | 1315 | /* this object doesn't have a previous entry */ |
| | 1316 | entry->ptr_.prev_free_ = VM_INVALID_OBJ; |
| | 1317 | |
| | 1318 | /* it's now the first entry in the list */ |
| | 1319 | first_free_ = id; |
| | 1320 | } |
| | 1321 | |
| | 1322 | |
| | 1323 | /* |
| | 1324 | * allocate a new page of objects |
| | 1325 | */ |
| | 1326 | void CVmObjTable::alloc_new_page() |
| | 1327 | { |
| | 1328 | size_t i; |
| | 1329 | vm_obj_id_t id; |
| | 1330 | CVmObjPageEntry *entry; |
| | 1331 | |
| | 1332 | /* first, make sure we have room in the master page list */ |
| | 1333 | if (pages_used_ == page_slots_) |
| | 1334 | { |
| | 1335 | /* increase the number of page slots */ |
| | 1336 | page_slots_ += 10; |
| | 1337 | |
| | 1338 | /* allocate space for the increased number of slots */ |
| | 1339 | pages_ = (CVmObjPageEntry **)t3realloc( |
| | 1340 | pages_, page_slots_ * sizeof(*pages_)); |
| | 1341 | } |
| | 1342 | |
| | 1343 | /* allocate a new page */ |
| | 1344 | pages_[pages_used_] = |
| | 1345 | (CVmObjPageEntry *)t3malloc(VM_OBJ_PAGE_CNT * sizeof(*pages_[0])); |
| | 1346 | |
| | 1347 | /* if that failed, throw an error */ |
| | 1348 | if (pages_[pages_used_] == 0) |
| | 1349 | err_throw(VMERR_OUT_OF_MEMORY); |
| | 1350 | |
| | 1351 | /* |
| | 1352 | * initialize the new page to be entirely free - add each element of |
| | 1353 | * the page to the free list |
| | 1354 | */ |
| | 1355 | entry = pages_[pages_used_]; |
| | 1356 | i = 0; |
| | 1357 | id = pages_used_ * VM_OBJ_PAGE_CNT; |
| | 1358 | |
| | 1359 | /* |
| | 1360 | * if this is the start of the very first page, leave off the first |
| | 1361 | * object, since its ID is invalid |
| | 1362 | */ |
| | 1363 | if (id == VM_INVALID_OBJ) |
| | 1364 | { |
| | 1365 | /* mark the object as free so we don't try to free it later */ |
| | 1366 | entry->free_ = TRUE; |
| | 1367 | |
| | 1368 | /* don't put the invalid object in the free list */ |
| | 1369 | ++i; |
| | 1370 | ++entry; |
| | 1371 | ++id; |
| | 1372 | } |
| | 1373 | |
| | 1374 | /* loop over each object in this page */ |
| | 1375 | for ( ; i < VM_OBJ_PAGE_CNT ; ++i, ++entry, ++id) |
| | 1376 | { |
| | 1377 | /* set the forward pointer in this object */ |
| | 1378 | entry->next_obj_ = first_free_; |
| | 1379 | |
| | 1380 | /* set the back pointer in the previous object in the list */ |
| | 1381 | if (first_free_ != VM_INVALID_OBJ) |
| | 1382 | get_entry(first_free_)->ptr_.prev_free_ = id; |
| | 1383 | |
| | 1384 | /* there's nothing before this entry yet */ |
| | 1385 | entry->ptr_.prev_free_ = VM_INVALID_OBJ; |
| | 1386 | |
| | 1387 | /* this is now the head of the list */ |
| | 1388 | first_free_ = id; |
| | 1389 | |
| | 1390 | /* mark it free */ |
| | 1391 | entry->free_ = TRUE; |
| | 1392 | |
| | 1393 | /* presume it's not part of the root set */ |
| | 1394 | entry->in_root_set_ = FALSE; |
| | 1395 | |
| | 1396 | /* |
| | 1397 | * mark it initially unreachable and finalized, since it's not |
| | 1398 | * even allocated yet |
| | 1399 | */ |
| | 1400 | entry->reachable_ = VMOBJ_UNREACHABLE; |
| | 1401 | entry->finalize_state_ = VMOBJ_FINALIZED; |
| | 1402 | } |
| | 1403 | |
| | 1404 | /* count the new page we've allocated */ |
| | 1405 | ++pages_used_; |
| | 1406 | } |
| | 1407 | |
| | 1408 | /* |
| | 1409 | * Add an object to the list of machine globals. |
| | 1410 | */ |
| | 1411 | void CVmObjTable::add_to_globals(vm_obj_id_t obj) |
| | 1412 | { |
| | 1413 | CVmObjGlobPage *pg; |
| | 1414 | |
| | 1415 | /* |
| | 1416 | * if this is a root set object, there is no need to mark it as |
| | 1417 | * global, since it is inherently uncollectable as an image file |
| | 1418 | * object to begin with |
| | 1419 | */ |
| | 1420 | if (get_entry(obj)->in_root_set_) |
| | 1421 | return; |
| | 1422 | |
| | 1423 | /* if we have any global pages allocated, try adding to the head page */ |
| | 1424 | if (globals_ != 0 && globals_->add_entry(obj)) |
| | 1425 | { |
| | 1426 | /* we successfully added it to the head page - we're done */ |
| | 1427 | return; |
| | 1428 | } |
| | 1429 | |
| | 1430 | /* |
| | 1431 | * either the head page is full, or we haven't allocated any global |
| | 1432 | * pages at all yet; in either case, allocate a new page and link it |
| | 1433 | * at the head of our list |
| | 1434 | */ |
| | 1435 | pg = new CVmObjGlobPage(); |
| | 1436 | pg->nxt_ = globals_; |
| | 1437 | globals_ = pg; |
| | 1438 | |
| | 1439 | /* |
| | 1440 | * add the object to the new page - it must fit, since the new page is |
| | 1441 | * empty |
| | 1442 | */ |
| | 1443 | globals_->add_entry(obj); |
| | 1444 | } |
| | 1445 | |
| | 1446 | /* |
| | 1447 | * Collect all garbage. This does a complete garbage collection pass, |
| | 1448 | * returning only after all unreachable objects have been collected. If |
| | 1449 | * incremental garbage collection is not required, the caller can simply |
| | 1450 | * invoke this routine to do the entire operation in a single call. |
| | 1451 | */ |
| | 1452 | void CVmObjTable::gc_full(VMG0_) |
| | 1453 | { |
| | 1454 | /* |
| | 1455 | * run the initial pass to mark globally-reachable objects, then run |
| | 1456 | * the garbage collector to completion |
| | 1457 | */ |
| | 1458 | gc_pass_init(vmg0_); |
| | 1459 | gc_pass_finish(vmg0_); |
| | 1460 | } |
| | 1461 | |
| | 1462 | /* |
| | 1463 | * Garbage collector - initialize. Add all globally-reachable objects |
| | 1464 | * to the work queue. |
| | 1465 | * |
| | 1466 | * We assume that the following initial conditions hold: all objects |
| | 1467 | * except root set objects are marked as unreferenced, and all root set |
| | 1468 | * objects are marked as referenced; all root set objects are in the GC |
| | 1469 | * work queue. So, we don't need to worry about finding root objects or |
| | 1470 | * initializing the other objects at this point. |
| | 1471 | */ |
| | 1472 | void CVmObjTable::gc_pass_init(VMG0_) |
| | 1473 | { |
| | 1474 | /* |
| | 1475 | * reset the allocations-since-gc counter, since this is now the |
| | 1476 | * last gc pass, and we obviously haven't performed any allocations |
| | 1477 | * since this gc pass yet |
| | 1478 | */ |
| | 1479 | allocs_since_gc_ = 0; |
| | 1480 | |
| | 1481 | /* trace objects reachable from the stack */ |
| | 1482 | gc_trace_stack(vmg0_); |
| | 1483 | |
| | 1484 | /* trace objects reachable from imports */ |
| | 1485 | gc_trace_imports(vmg0_); |
| | 1486 | |
| | 1487 | /* trace objects reachable from machine globals */ |
| | 1488 | gc_trace_globals(vmg0_); |
| | 1489 | |
| | 1490 | /* |
| | 1491 | * Process undo records - for each undo record, mark any referenced |
| | 1492 | * objects as reachable. Undo records are part of the root set. |
| | 1493 | */ |
| | 1494 | G_undo->gc_mark_refs(vmg0_); |
| | 1495 | } |
| | 1496 | |
| | 1497 | /* |
| | 1498 | * Garbage collection - continue processing the work queue. This |
| | 1499 | * processes a set of entries from the work queue. This routine can be |
| | 1500 | * used for incremental garbage collection: after calling |
| | 1501 | * gc_pass_init(), the caller can repeatedly invoke this routine until |
| | 1502 | * it returns false. Since this routine will return after a short time |
| | 1503 | * even if there's more work left to do, other operations (such as |
| | 1504 | * processing user input) can be interleaved in a single thread with |
| | 1505 | * garbage collection. |
| | 1506 | * |
| | 1507 | * The actual number of entries that we process is configurable at VM |
| | 1508 | * compile-time via VM_GC_WORK_INCREMENT. The point of running the GC |
| | 1509 | * incrementally is to allow GC work to be interleaved with long-running |
| | 1510 | * user I/O operations (such as reading a line of text from the |
| | 1511 | * keyboard) in the foreground thread, so the work increment should be |
| | 1512 | * chosen so that each call to this routine completes quickly enough |
| | 1513 | * that the user will perceive no delay. |
| | 1514 | * |
| | 1515 | * Returns true if more work remains to be done, false if not. The |
| | 1516 | * caller should invoke this routine repeatedly until it returns false. |
| | 1517 | */ |
| | 1518 | int CVmObjTable::gc_pass_continue(VMG_ int trace_transient) |
| | 1519 | { |
| | 1520 | int cnt; |
| | 1521 | |
| | 1522 | /* |
| | 1523 | * keep going until we exhaust the queue or run for our maximum |
| | 1524 | * number of iterations |
| | 1525 | */ |
| | 1526 | for (cnt = VM_GC_WORK_INCREMENT ; |
| | 1527 | cnt != 0 && gc_queue_head_ != VM_INVALID_OBJ ; --cnt) |
| | 1528 | { |
| | 1529 | vm_obj_id_t cur; |
| | 1530 | CVmObjPageEntry *entry; |
| | 1531 | |
| | 1532 | /* get the next item from the work queue */ |
| | 1533 | cur = gc_queue_head_; |
| | 1534 | |
| | 1535 | /* get this object's entry */ |
| | 1536 | entry = get_entry(cur); |
| | 1537 | |
| | 1538 | /* remove this entry from the work queue */ |
| | 1539 | gc_queue_head_ = entry->next_obj_; |
| | 1540 | |
| | 1541 | /* |
| | 1542 | * Tell this object to mark its references. Mark the referenced |
| | 1543 | * objects with the same state as this object, if they're not |
| | 1544 | * already marked with a stronger state. |
| | 1545 | * |
| | 1546 | * If we're not tracing transients, do not trace this object if |
| | 1547 | * it's transient. |
| | 1548 | */ |
| | 1549 | if (trace_transient || !entry->transient_) |
| | 1550 | entry->get_vm_obj()->mark_refs(vmg_ entry->reachable_); |
| | 1551 | } |
| | 1552 | |
| | 1553 | /* |
| | 1554 | * return true if there's more work to do; there's more work to do |
| | 1555 | * if we have any objects left in the gc work queue |
| | 1556 | */ |
| | 1557 | return gc_queue_head_ != VM_INVALID_OBJ; |
| | 1558 | } |
| | 1559 | |
| | 1560 | /* |
| | 1561 | * Finish garbage collection. We'll finish any work remaining in the |
| | 1562 | * work queue, so this is safe to call at any time after gc_pass_init(), |
| | 1563 | * after any number of calls (even zero) to gc_pass_continue(). |
| | 1564 | */ |
| | 1565 | void CVmObjTable::gc_pass_finish(VMG0_) |
| | 1566 | { |
| | 1567 | CVmObjPageEntry **pg; |
| | 1568 | CVmObjPageEntry *entry; |
| | 1569 | size_t i; |
| | 1570 | size_t j; |
| | 1571 | vm_obj_id_t id; |
| | 1572 | |
| | 1573 | /* |
| | 1574 | * Make sure we're done processing the work queue -- keep calling |
| | 1575 | * gc_pass_continue() until it indicates that it's finished. If |
| | 1576 | * we're skipping finalizers, stop as soon as the state structure |
| | 1577 | * indicates that we've started running finalizers. |
| | 1578 | */ |
| | 1579 | gc_trace_work_queue(vmg_ TRUE); |
| | 1580 | |
| | 1581 | /* |
| | 1582 | * We've now marked everything that's reachable from the root set as |
| | 1583 | * VMOBJ_REACHABLE. We can therefore determine the set of objects |
| | 1584 | * that are newly 'finalizable' - an object becomes finalizable when |
| | 1585 | * it was previously 'unfinalizable' and is not reachable, because we |
| | 1586 | * can finalize an object any time after it first becomes unreachable. |
| | 1587 | * So, scan all objects for eligibility for the 'finalizable' |
| | 1588 | * transition, and make the transition in those objects. |
| | 1589 | */ |
| | 1590 | for (id = 0, i = pages_used_, pg = pages_ ; i > 0 ; ++pg, --i) |
| | 1591 | { |
| | 1592 | /* go through each entry on this page */ |
| | 1593 | for (j = VM_OBJ_PAGE_CNT, entry = *pg ; j > 0 ; --j, ++entry, ++id) |
| | 1594 | { |
| | 1595 | /* |
| | 1596 | * if this entry is not free and is not in the root set, check |
| | 1597 | * to see if its finalization status is changing |
| | 1598 | */ |
| | 1599 | if (!entry->free_ && !entry->in_root_set_) |
| | 1600 | { |
| | 1601 | /* |
| | 1602 | * If the entry is not reachable, and was previously |
| | 1603 | * unfinalizable, it is now finalizable. |
| | 1604 | * |
| | 1605 | * Note that an object must actually be reachable to avoid |
| | 1606 | * become finalizable at this point. Not only do |
| | 1607 | * unreachable objects become finalizable, but |
| | 1608 | * 'f-reachable' objects do, too, since these are only |
| | 1609 | * reachable from other finalizable objects. |
| | 1610 | */ |
| | 1611 | if (entry->reachable_ != VMOBJ_REACHABLE |
| | 1612 | && entry->finalize_state_ == VMOBJ_UNFINALIZABLE) |
| | 1613 | { |
| | 1614 | /* |
| | 1615 | * This object is newly finalizable. If it has no |
| | 1616 | * finalizer, the object can go directly to the |
| | 1617 | * 'finalized' state; otherwise, add it to the queue |
| | 1618 | * of objects with pending finalizers. |
| | 1619 | */ |
| | 1620 | if (entry->get_vm_obj()->has_finalizer(vmg_ id)) |
| | 1621 | { |
| | 1622 | /* |
| | 1623 | * This object is not reachable from the root set |
| | 1624 | * and was previously unfinalizable. Make the |
| | 1625 | * object finalizable. |
| | 1626 | */ |
| | 1627 | entry->finalize_state_ = VMOBJ_FINALIZABLE; |
| | 1628 | } |
| | 1629 | else |
| | 1630 | { |
| | 1631 | /* |
| | 1632 | * the entry has no finalizer, so we can make this |
| | 1633 | * object 'finalized' immediately |
| | 1634 | */ |
| | 1635 | entry->finalize_state_ = VMOBJ_FINALIZED; |
| | 1636 | } |
| | 1637 | } |
| | 1638 | |
| | 1639 | /* |
| | 1640 | * If this object is finalizable, add it to the work queue |
| | 1641 | * in state "f-reachable." We must mark everything this |
| | 1642 | * object references, directly or indirectly, as |
| | 1643 | * f-reachable, which we'll do with another pass through |
| | 1644 | * the gc queue momentarily. |
| | 1645 | */ |
| | 1646 | if (entry->finalize_state_ == VMOBJ_FINALIZABLE) |
| | 1647 | { |
| | 1648 | /* |
| | 1649 | * this object and all of the objects it references |
| | 1650 | * are "f-reachable" |
| | 1651 | */ |
| | 1652 | add_to_gc_queue(id, entry, VMOBJ_F_REACHABLE); |
| | 1653 | } |
| | 1654 | } |
| | 1655 | } |
| | 1656 | } |
| | 1657 | |
| | 1658 | /* |
| | 1659 | * During the scan above, we put all of the finalizable objects in the |
| | 1660 | * work queue in reachability state 'f-reachable'. (Actually, |
| | 1661 | * finalizable objects that were fully reachable were not put in the |
| | 1662 | * work queue, because they are in a stronger reachability state that |
| | 1663 | * we've already fully scanned.) Trace the work queue so that we mark |
| | 1664 | * everything reachable indirectly from an f-reachable object as also |
| | 1665 | * being at least f-reachable. |
| | 1666 | */ |
| | 1667 | gc_trace_work_queue(vmg_ TRUE); |
| | 1668 | |
| | 1669 | /* |
| | 1670 | * We have now marked everything that's fully reachable as being in |
| | 1671 | * state 'reachable', and everything that's reachable from a |
| | 1672 | * finalizable object as being in state 'f-reachable'. Anything that |
| | 1673 | * is still in state 'unreachable' is garbage and can be collected. |
| | 1674 | */ |
| | 1675 | for (id = 0, i = pages_used_, pg = pages_ ; i > 0 ; ++pg, --i) |
| | 1676 | { |
| | 1677 | /* go through each entry on this page */ |
| | 1678 | for (j = VM_OBJ_PAGE_CNT, entry = *pg ; j > 0 ; --j, ++entry, ++id) |
| | 1679 | { |
| | 1680 | /* if it's not already free, process it */ |
| | 1681 | if (!entry->free_) |
| | 1682 | { |
| | 1683 | /* if the object is deletable, delete it */ |
| | 1684 | if (entry->is_deletable()) |
| | 1685 | { |
| | 1686 | /* |
| | 1687 | * This object is completely unreachable, and it has |
| | 1688 | * already been finalized. This means there is no |
| | 1689 | * possibility that the object could ever become |
| | 1690 | * reachable again, hence we can discard the object. |
| | 1691 | */ |
| | 1692 | delete_entry(vmg_ id, entry); |
| | 1693 | } |
| | 1694 | else |
| | 1695 | { |
| | 1696 | /* |
| | 1697 | * The object is reachable, so keep it around. Since |
| | 1698 | * it's staying around, and since we know which |
| | 1699 | * objects we're deleting and which are staying, we |
| | 1700 | * can ask this object to remove all of its "stale |
| | 1701 | * weak references" - that is, weak references to |
| | 1702 | * objects that we're about to delete. Don't bother |
| | 1703 | * notifying the object if it's incapable of keeping |
| | 1704 | * weak references. |
| | 1705 | */ |
| | 1706 | if (entry->can_have_weak_refs_) |
| | 1707 | entry->get_vm_obj()->remove_stale_weak_refs(vmg0_); |
| | 1708 | |
| | 1709 | /* |
| | 1710 | * If this object is finalizable, put it in the |
| | 1711 | * finalizer queue, so that we can run its finalizer |
| | 1712 | * when we're done scanning the table. |
| | 1713 | */ |
| | 1714 | if (entry->finalize_state_ == VMOBJ_FINALIZABLE) |
| | 1715 | add_to_finalize_queue(id, entry); |
| | 1716 | |
| | 1717 | /* |
| | 1718 | * restore initial conditions for this object, so that |
| | 1719 | * we're properly set up for the next GC pass |
| | 1720 | */ |
| | 1721 | gc_set_init_conditions(id, entry); |
| | 1722 | } |
| | 1723 | } |
| | 1724 | } |
| | 1725 | } |
| | 1726 | |
| | 1727 | /* |
| | 1728 | * Go through the undo records and clear any stale weak references |
| | 1729 | * contained in the undo list. |
| | 1730 | */ |
| | 1731 | G_undo->gc_remove_stale_weak_refs(vmg0_); |
| | 1732 | |
| | 1733 | /* |
| | 1734 | * All of the finalizable objects are now in the finalizer queue. Run |
| | 1735 | * through the finalizer queue and run each such object's finalizer. |
| | 1736 | */ |
| | 1737 | run_finalizers(vmg0_); |
| | 1738 | } |
| | 1739 | |
| | 1740 | /* |
| | 1741 | * Trace all objects reachable from the work queue. |
| | 1742 | */ |
| | 1743 | void CVmObjTable::gc_trace_work_queue(VMG_ int trace_transient) |
| | 1744 | { |
| | 1745 | /* |
| | 1746 | * trace everything reachable directly from the work queue, until we |
| | 1747 | * exhaust the queue |
| | 1748 | */ |
| | 1749 | while (gc_pass_continue(vmg_ trace_transient)) ; |
| | 1750 | } |
| | 1751 | |
| | 1752 | /* |
| | 1753 | * Garbage collection: trace objects reachable from the stack |
| | 1754 | */ |
| | 1755 | void CVmObjTable::gc_trace_stack(VMG0_) |
| | 1756 | { |
| | 1757 | size_t i; |
| | 1758 | size_t depth; |
| | 1759 | vm_val_t *val; |
| | 1760 | |
| | 1761 | /* |
| | 1762 | * Process the stack. For each stack element that refers to an |
| | 1763 | * object, mark the object as referenced and add it to the work |
| | 1764 | * queue. |
| | 1765 | * |
| | 1766 | * Note that it makes no difference in what order we process the |
| | 1767 | * stack elements; we go from depth down to 0 merely as a trivial |
| | 1768 | * micro-optimization to avoid evaluating the stack depth on every |
| | 1769 | * iteration of the loop. |
| | 1770 | */ |
| | 1771 | for (i = 0, depth = G_stk->get_depth() ; i < depth ; ++i) |
| | 1772 | { |
| | 1773 | /* |
| | 1774 | * If this element refers to an object, and the object hasn't |
| | 1775 | * already been marked as referenced, mark it as reachable and |
| | 1776 | * add it to the work queue. |
| | 1777 | * |
| | 1778 | * Note that we only have to worry about objects here. We don't |
| | 1779 | * have to worry about constant lists, even though they can |
| | 1780 | * contain object references, because any object reference in a |
| | 1781 | * constant list must be a root set object, and we've already |
| | 1782 | * processed all root set objects. |
| | 1783 | */ |
| | 1784 | val = G_stk->get(i); |
| | 1785 | if (val->typ == VM_OBJ && val->val.obj != VM_INVALID_OBJ) |
| | 1786 | add_to_gc_queue(val->val.obj, VMOBJ_REACHABLE); |
| | 1787 | } |
| | 1788 | } |
| | 1789 | |
| | 1790 | /* |
| | 1791 | * Trace objects reachable from imports |
| | 1792 | */ |
| | 1793 | void CVmObjTable::gc_trace_imports(VMG0_) |
| | 1794 | { |
| | 1795 | /* |
| | 1796 | * generate the list of object imports; for each one, if we have a |
| | 1797 | * valid object for the import, mark it as reachable |
| | 1798 | */ |
| | 1799 | #define VM_IMPORT_OBJ(sym, mem) \ |
| | 1800 | if (G_predef->mem != VM_INVALID_OBJ) \ |
| | 1801 | add_to_gc_queue(G_predef->mem, VMOBJ_REACHABLE); |
| | 1802 | #define VM_NOIMPORT_OBJ(sym, mem) VM_IMPORT_OBJ(sym, mem) |
| | 1803 | #include "vmimport.h" |
| | 1804 | } |
| | 1805 | |
| | 1806 | /* |
| | 1807 | * Garbage collection: trace objects reachable from the machine globals |
| | 1808 | */ |
| | 1809 | void CVmObjTable::gc_trace_globals(VMG0_) |
| | 1810 | { |
| | 1811 | CVmObjGlobPage *pg; |
| | 1812 | vm_val_t *val; |
| | 1813 | vm_globalvar_t *var; |
| | 1814 | |
| | 1815 | /* trace each page of globals */ |
| | 1816 | for (pg = globals_ ; pg != 0 ; pg = pg->nxt_) |
| | 1817 | { |
| | 1818 | size_t i; |
| | 1819 | vm_obj_id_t *objp; |
| | 1820 | |
| | 1821 | /* trace each item on this page */ |
| | 1822 | for (objp = pg->objs_, i = pg->used_ ; i != 0 ; ++objp, --i) |
| | 1823 | { |
| | 1824 | /* trace this global */ |
| | 1825 | add_to_gc_queue(*objp, VMOBJ_REACHABLE); |
| | 1826 | } |
| | 1827 | } |
| | 1828 | |
| | 1829 | /* the return value register (R0) is a machine global */ |
| | 1830 | val = G_interpreter->get_r0(); |
| | 1831 | if (val->typ == VM_OBJ && val->val.obj != VM_INVALID_OBJ) |
| | 1832 | add_to_gc_queue(val->val.obj, VMOBJ_REACHABLE); |
| | 1833 | |
| | 1834 | /* trace the global variables defined by other subsystems */ |
| | 1835 | for (var = global_var_head_ ; var != 0 ; var = var->nxt) |
| | 1836 | { |
| | 1837 | /* if this global variable contains an object, trace it */ |
| | 1838 | if (var->val.typ == VM_OBJ && var->val.val.obj != VM_INVALID_OBJ) |
| | 1839 | add_to_gc_queue(var->val.val.obj, VMOBJ_REACHABLE); |
| | 1840 | } |
| | 1841 | } |
| | 1842 | |
| | 1843 | #if 0 // moved inline, as it's small and is called a fair amount |
| | 1844 | /* |
| | 1845 | * Set the initial conditions for an object, in preparation for the next |
| | 1846 | * GC pass. |
| | 1847 | */ |
| | 1848 | void CVmObjTable::gc_set_init_conditions(vm_obj_id_t id, |
| | 1849 | CVmObjPageEntry *entry) |
| | 1850 | { |
| | 1851 | /* |
| | 1852 | * Mark the object as unreachable -- at the start of each GC pass, |
| | 1853 | * all non-root-set objects must be marked unreachable. |
| | 1854 | */ |
| | 1855 | entry->reachable_ = VMOBJ_UNREACHABLE; |
| | 1856 | |
| | 1857 | /* |
| | 1858 | * If it's in the root set, add it to the GC work queue -- all |
| | 1859 | * root-set objects must be in the work queue and marked as reachable |
| | 1860 | * at the start of each GC pass. |
| | 1861 | * |
| | 1862 | * If the object is not in the root set, check to see if it's |
| | 1863 | * finalizable. If so, add it to the finalizer queue, so that we |
| | 1864 | * eventually run its finalizer. |
| | 1865 | */ |
| | 1866 | if (entry->in_root_set_) |
| | 1867 | add_to_gc_queue(id, entry, VMOBJ_REACHABLE); |
| | 1868 | } |
| | 1869 | #endif |
| | 1870 | |
| | 1871 | /* |
| | 1872 | * Run finalizers |
| | 1873 | */ |
| | 1874 | void CVmObjTable::run_finalizers(VMG0_) |
| | 1875 | { |
| | 1876 | /* keep going until we run out of work or reach our work limit */ |
| | 1877 | while (finalize_queue_head_ != VM_INVALID_OBJ) |
| | 1878 | { |
| | 1879 | CVmObjPageEntry *entry; |
| | 1880 | vm_obj_id_t id; |
| | 1881 | |
| | 1882 | /* get the next object from the queue */ |
| | 1883 | id = finalize_queue_head_; |
| | 1884 | |
| | 1885 | /* get the entry */ |
| | 1886 | entry = get_entry(id); |
| | 1887 | |
| | 1888 | /* remove the entry form the queue */ |
| | 1889 | finalize_queue_head_ = entry->next_obj_; |
| | 1890 | |
| | 1891 | /* mark the object as finalized */ |
| | 1892 | entry->finalize_state_ = VMOBJ_FINALIZED; |
| | 1893 | |
| | 1894 | /* |
| | 1895 | * the entry is no longer in any queue, so we must mark it as |
| | 1896 | * unreachable -- this ensures that the initial conditions are |
| | 1897 | * correct for the next garbage collection pass, since all |
| | 1898 | * objects not in the work queue must be marked as unreachable |
| | 1899 | * (it doesn't matter whether the object is actually reachable; |
| | 1900 | * the garbage collector will make that determination when it |
| | 1901 | * next runs) |
| | 1902 | */ |
| | 1903 | entry->reachable_ = VMOBJ_UNREACHABLE; |
| | 1904 | |
| | 1905 | /* invoke its finalizer */ |
| | 1906 | entry->get_vm_obj()->invoke_finalizer(vmg_ id); |
| | 1907 | } |
| | 1908 | } |
| | 1909 | |
| | 1910 | |
| | 1911 | /* |
| | 1912 | * Receive notification that we're creating a new undo savepoint |
| | 1913 | */ |
| | 1914 | void CVmObjTable::notify_new_savept() |
| | 1915 | { |
| | 1916 | CVmObjPageEntry **pg; |
| | 1917 | CVmObjPageEntry *entry; |
| | 1918 | size_t i; |
| | 1919 | size_t j; |
| | 1920 | vm_obj_id_t id; |
| | 1921 | |
| | 1922 | /* go through each page of objects */ |
| | 1923 | for (id = 0, i = pages_used_, pg = pages_ ; i > 0 ; ++pg, --i) |
| | 1924 | { |
| | 1925 | /* go through each entry on this page */ |
| | 1926 | for (j = VM_OBJ_PAGE_CNT, entry = *pg ; j > 0 ; --j, ++entry, ++id) |
| | 1927 | { |
| | 1928 | /* if this entry is active, tell it about the new savepoint */ |
| | 1929 | if (!entry->free_) |
| | 1930 | { |
| | 1931 | /* |
| | 1932 | * the object existed at the start of this savepoint, so |
| | 1933 | * it must keep undo information throughout the savepoint |
| | 1934 | */ |
| | 1935 | entry->in_undo_ = TRUE; |
| | 1936 | |
| | 1937 | /* notify the object of the new savepoint */ |
| | 1938 | entry->get_vm_obj()->notify_new_savept(); |
| | 1939 | } |
| | 1940 | } |
| | 1941 | } |
| | 1942 | } |
| | 1943 | |
| | 1944 | /* |
| | 1945 | * Apply undo |
| | 1946 | */ |
| | 1947 | void CVmObjTable::apply_undo(VMG_ CVmUndoRecord *rec) |
| | 1948 | { |
| | 1949 | /* tell the object to apply the undo */ |
| | 1950 | if (rec->obj != VM_INVALID_OBJ) |
| | 1951 | get_obj(rec->obj)->apply_undo(vmg_ rec); |
| | 1952 | } |
| | 1953 | |
| | 1954 | |
| | 1955 | /* |
| | 1956 | * Scan all objects and add metaclass entries to the metaclass |
| | 1957 | * dependency table for any metaclasses of which there are existing |
| | 1958 | * instances. |
| | 1959 | */ |
| | 1960 | void CVmObjTable::add_metadeps_for_instances(VMG0_) |
| | 1961 | { |
| | 1962 | CVmObjPageEntry **pg; |
| | 1963 | size_t i; |
| | 1964 | vm_obj_id_t id; |
| | 1965 | |
| | 1966 | /* go through each page in the object table */ |
| | 1967 | for (id = 0, i = pages_used_, pg = pages_ ; i > 0 ; ++pg, --i) |
| | 1968 | { |
| | 1969 | size_t j; |
| | 1970 | CVmObjPageEntry *entry; |
| | 1971 | |
| | 1972 | /* start at the start of the page, but skip object ID = 0 */ |
| | 1973 | j = VM_OBJ_PAGE_CNT; |
| | 1974 | entry = *pg; |
| | 1975 | |
| | 1976 | /* go through each entry on this page */ |
| | 1977 | for ( ; j > 0 ; --j, ++entry, ++id) |
| | 1978 | { |
| | 1979 | /* if this entry is in use, add its metaclass if necessary */ |
| | 1980 | if (!entry->free_) |
| | 1981 | G_meta_table->add_entry_if_new( |
| | 1982 | entry->get_vm_obj()->get_metaclass_reg()->get_reg_idx(), |
| | 1983 | 0, VM_INVALID_PROP, VM_INVALID_PROP); |
| | 1984 | } |
| | 1985 | } |
| | 1986 | } |
| | 1987 | |
| | 1988 | |
| | 1989 | /* ------------------------------------------------------------------------ */ |
| | 1990 | /* |
| | 1991 | * creation |
| | 1992 | */ |
| | 1993 | CVmObjFixup::CVmObjFixup(ulong entry_cnt) |
| | 1994 | { |
| | 1995 | uint i; |
| | 1996 | |
| | 1997 | /* remember the number of entries */ |
| | 1998 | cnt_ = entry_cnt; |
| | 1999 | |
| | 2000 | /* no entries are used yet */ |
| | 2001 | used_ = 0; |
| | 2002 | |
| | 2003 | /* if we have no entries, there's nothing to do */ |
| | 2004 | if (cnt_ == 0) |
| | 2005 | { |
| | 2006 | arr_ = 0; |
| | 2007 | pages_ = 0; |
| | 2008 | return; |
| | 2009 | } |
| | 2010 | |
| | 2011 | /* calculate the number of subarrays we need */ |
| | 2012 | pages_ = (entry_cnt + VMOBJFIXUP_SUB_SIZE - 1) / VMOBJFIXUP_SUB_SIZE; |
| | 2013 | |
| | 2014 | /* allocate the necessary number of subarrays */ |
| | 2015 | arr_ = (obj_fixup_entry **)t3malloc(pages_ * sizeof(arr_[0])); |
| | 2016 | |
| | 2017 | /* allocate the subarrays */ |
| | 2018 | for (i = 0 ; i < pages_ ; ++i) |
| | 2019 | { |
| | 2020 | size_t cur_cnt; |
| | 2021 | |
| | 2022 | /* |
| | 2023 | * allocate a full page, except for the last page, which might be |
| | 2024 | * only partially used |
| | 2025 | */ |
| | 2026 | cur_cnt = VMOBJFIXUP_SUB_SIZE; |
| | 2027 | if (i + 1 == pages_) |
| | 2028 | cur_cnt = ((entry_cnt - 1) % VMOBJFIXUP_SUB_SIZE) + 1; |
| | 2029 | |
| | 2030 | /* allocate it */ |
| | 2031 | arr_[i] = (obj_fixup_entry *)t3malloc(cur_cnt * sizeof(arr_[i][i])); |
| | 2032 | } |
| | 2033 | } |
| | 2034 | |
| | 2035 | /* |
| | 2036 | * deletion |
| | 2037 | */ |
| | 2038 | CVmObjFixup::~CVmObjFixup() |
| | 2039 | { |
| | 2040 | uint i; |
| | 2041 | |
| | 2042 | /* if we never allocated an array, there's nothing to do */ |
| | 2043 | if (arr_ == 0) |
| | 2044 | return; |
| | 2045 | |
| | 2046 | /* delete each subarray */ |
| | 2047 | for (i = 0 ; i < pages_ ; ++i) |
| | 2048 | t3free(arr_[i]); |
| | 2049 | |
| | 2050 | /* delete the main array */ |
| | 2051 | t3free(arr_); |
| | 2052 | } |
| | 2053 | |
| | 2054 | /* |
| | 2055 | * add a fixup |
| | 2056 | */ |
| | 2057 | void CVmObjFixup::add_fixup(vm_obj_id_t old_id, vm_obj_id_t new_id) |
| | 2058 | { |
| | 2059 | obj_fixup_entry *entry; |
| | 2060 | |
| | 2061 | /* allocate the next available entry */ |
| | 2062 | entry = get_entry(used_++); |
| | 2063 | |
| | 2064 | /* store it */ |
| | 2065 | entry->old_id = old_id; |
| | 2066 | entry->new_id = new_id; |
| | 2067 | } |
| | 2068 | |
| | 2069 | /* |
| | 2070 | * translate an ID |
| | 2071 | */ |
| | 2072 | vm_obj_id_t CVmObjFixup::get_new_id(VMG_ vm_obj_id_t old_id) |
| | 2073 | { |
| | 2074 | obj_fixup_entry *entry; |
| | 2075 | |
| | 2076 | /* |
| | 2077 | * if it's a root-set object, don't bother even trying to translate |
| | 2078 | * it, because root-set objects have stable ID's that never change on |
| | 2079 | * saving or restoring |
| | 2080 | */ |
| | 2081 | if (G_obj_table->is_obj_id_valid(old_id) |
| | 2082 | && G_obj_table->is_obj_in_root_set(old_id)) |
| | 2083 | return old_id; |
| | 2084 | |
| | 2085 | /* find the entry by the object ID */ |
| | 2086 | entry = find_entry(old_id); |
| | 2087 | |
| | 2088 | /* |
| | 2089 | * if we found it, return the new ID; otherwise, return the old ID |
| | 2090 | * unchanged, since the ID evidently doesn't require mapping |
| | 2091 | */ |
| | 2092 | return (entry != 0 ? entry->new_id : old_id); |
| | 2093 | } |
| | 2094 | |
| | 2095 | /* |
| | 2096 | * find an entry given the old object ID |
| | 2097 | */ |
| | 2098 | obj_fixup_entry *CVmObjFixup::find_entry(vm_obj_id_t old_id) |
| | 2099 | { |
| | 2100 | ulong lo; |
| | 2101 | ulong hi; |
| | 2102 | ulong cur; |
| | 2103 | |
| | 2104 | /* do a binary search for the entry */ |
| | 2105 | lo = 0; |
| | 2106 | hi = cnt_ - 1; |
| | 2107 | while (lo <= hi) |
| | 2108 | { |
| | 2109 | obj_fixup_entry *entry; |
| | 2110 | |
| | 2111 | /* split the difference */ |
| | 2112 | cur = lo + (hi - lo)/2; |
| | 2113 | entry = get_entry(cur); |
| | 2114 | |
| | 2115 | /* is it the one we're looking for? */ |
| | 2116 | if (entry->old_id == old_id) |
| | 2117 | { |
| | 2118 | /* it's the one - return the entry */ |
| | 2119 | return entry; |
| | 2120 | } |
| | 2121 | else if (old_id > entry->old_id) |
| | 2122 | { |
| | 2123 | /* we need to go higher */ |
| | 2124 | lo = (cur == lo ? cur + 1 : cur); |
| | 2125 | } |
| | 2126 | else |
| | 2127 | { |
| | 2128 | /* we need to go lower */ |
| | 2129 | hi = (cur == hi ? cur - 1 : cur); |
| | 2130 | } |
| | 2131 | } |
| | 2132 | |
| | 2133 | /* didn't find it */ |
| | 2134 | return 0; |
| | 2135 | } |
| | 2136 | |
| | 2137 | /* |
| | 2138 | * Fix a DATAHOLDER value |
| | 2139 | */ |
| | 2140 | void CVmObjFixup::fix_dh(VMG_ char *dh) |
| | 2141 | { |
| | 2142 | /* if it's an object, translate the ID */ |
| | 2143 | if (vmb_get_dh_type(dh) == VM_OBJ) |
| | 2144 | { |
| | 2145 | vm_obj_id_t id; |
| | 2146 | |
| | 2147 | /* get the object value */ |
| | 2148 | id = vmb_get_dh_obj(dh); |
| | 2149 | |
| | 2150 | /* translate it */ |
| | 2151 | id = get_new_id(vmg_ id); |
| | 2152 | |
| | 2153 | /* |
| | 2154 | * if it's invalid, set the dataholder value to nil; otherwise, |
| | 2155 | * set it to the new object ID |
| | 2156 | */ |
| | 2157 | if (id == VM_INVALID_OBJ) |
| | 2158 | vmb_put_dh_nil(dh); |
| | 2159 | else |
| | 2160 | vmb_put_dh_obj(dh, id); |
| | 2161 | } |
| | 2162 | } |
| | 2163 | |
| | 2164 | /* |
| | 2165 | * Fix up an array of DATAHOLDER values. |
| | 2166 | */ |
| | 2167 | void CVmObjFixup::fix_dh_array(VMG_ char *arr, size_t cnt) |
| | 2168 | { |
| | 2169 | /* scan the array of dataholders */ |
| | 2170 | for ( ; cnt != 0 ; --cnt, arr += VMB_DATAHOLDER) |
| | 2171 | fix_dh(vmg_ arr); |
| | 2172 | } |
| | 2173 | |
| | 2174 | /* |
| | 2175 | * Fix a portable VMB_OBJECT_ID field |
| | 2176 | */ |
| | 2177 | void CVmObjFixup::fix_vmb_obj(VMG_ char *p) |
| | 2178 | { |
| | 2179 | vm_obj_id_t id; |
| | 2180 | |
| | 2181 | /* get the old ID */ |
| | 2182 | id = vmb_get_objid(p); |
| | 2183 | |
| | 2184 | /* fix it up */ |
| | 2185 | id = get_new_id(vmg_ id); |
| | 2186 | |
| | 2187 | /* store it back */ |
| | 2188 | vmb_put_objid(p, id); |
| | 2189 | } |
| | 2190 | |
| | 2191 | /* |
| | 2192 | * Fix an array of portable VMB_OBJECT_ID fields |
| | 2193 | */ |
| | 2194 | void CVmObjFixup::fix_vmb_obj_array(VMG_ char *p, size_t cnt) |
| | 2195 | { |
| | 2196 | /* scan the array */ |
| | 2197 | for ( ; cnt != 0 ; --cnt, p += VMB_OBJECT_ID) |
| | 2198 | fix_vmb_obj(vmg_ p); |
| | 2199 | } |
| | 2200 | |
| | 2201 | |
| | 2202 | /* ------------------------------------------------------------------------ */ |
| | 2203 | /* |
| | 2204 | * Save/restore |
| | 2205 | */ |
| | 2206 | |
| | 2207 | /* |
| | 2208 | * table-of-contents flags |
| | 2209 | */ |
| | 2210 | |
| | 2211 | /* object is transient (so the object isn't saved to the state file) */ |
| | 2212 | #define VMOBJ_TOC_TRANSIENT 0x0001 |
| | 2213 | |
| | 2214 | |
| | 2215 | /* |
| | 2216 | * Save state to a file |
| | 2217 | */ |
| | 2218 | void CVmObjTable::save(VMG_ CVmFile *fp) |
| | 2219 | { |
| | 2220 | CVmObjPageEntry **pg; |
| | 2221 | CVmObjPageEntry *entry; |
| | 2222 | size_t i; |
| | 2223 | size_t j; |
| | 2224 | vm_obj_id_t id; |
| | 2225 | size_t toc_cnt; |
| | 2226 | size_t save_cnt; |
| | 2227 | long toc_cnt_pos; |
| | 2228 | long end_pos; |
| | 2229 | |
| | 2230 | /* |
| | 2231 | * Before we save, perform a full GC pass. This will ensure that we |
| | 2232 | * have removed all objects that are referenced only weakly, and |
| | 2233 | * cleaned up the weak references to them; this is important because |
| | 2234 | * we don't trace weak references for the purposes of calculating the |
| | 2235 | * set of objects that must be saved, and hence won't save any objects |
| | 2236 | * that are only weakly referenced, which would leave dangling |
| | 2237 | * references in the saved state if those weak references weren't |
| | 2238 | * cleaned up before the objects containing them are saved. |
| | 2239 | */ |
| | 2240 | gc_full(vmg0_); |
| | 2241 | |
| | 2242 | /* |
| | 2243 | * Make sure that all of the metaclasses that we are actually using |
| | 2244 | * are in the metaclass dependency table. We store the table in the |
| | 2245 | * file, because the table provides the mapping from file-local |
| | 2246 | * metaclass ID's to actual metaclasses; we must make sure that the |
| | 2247 | * table is complete (i.e., contains an entry for each metaclass of |
| | 2248 | * which there is an instance) before storing the table. |
| | 2249 | */ |
| | 2250 | add_metadeps_for_instances(vmg0_); |
| | 2251 | |
| | 2252 | /* save the metaclass table */ |
| | 2253 | G_meta_table->write_to_file(fp); |
| | 2254 | |
| | 2255 | /* |
| | 2256 | * Figure out what objects we need to save. We only need to save |
| | 2257 | * objects that are directly reachable from the root object set, from |
| | 2258 | * the imports, or from the globals. |
| | 2259 | * |
| | 2260 | * We don't need to save objects that are only accessible from the |
| | 2261 | * undo, because we don't save any undo information in the file. We |
| | 2262 | * also don't need to save any objects that are reachable only from |
| | 2263 | * the stack, since the stack is inherently transient. |
| | 2264 | * |
| | 2265 | * Note that we don't need to trace from transient objects, since we |
| | 2266 | * won't be saving the transient objects and thus won't need to save |
| | 2267 | * anything referenced only from transient objects. |
| | 2268 | * |
| | 2269 | * So, we merely trace objects reachable from the imports, globals, |
| | 2270 | * and work queue. At any time between GC passes, the work queue |
| | 2271 | * contains the complete list of root-set objects, hence we can simply |
| | 2272 | * trace from the current work queue. |
| | 2273 | */ |
| | 2274 | gc_trace_imports(vmg0_); |
| | 2275 | gc_trace_globals(vmg0_); |
| | 2276 | gc_trace_work_queue(vmg_ FALSE); |
| | 2277 | |
| | 2278 | /* |
| | 2279 | * Before we save the objects themselves, save a table of contents of |
| | 2280 | * the dynamically-allocated objects to be saved. This table of |
| | 2281 | * contents will allow us to fix up references to objects on reloading |
| | 2282 | * the file with the new object numbers we assign them at that time. |
| | 2283 | * First, write a placeholder for the table of contents entry count. |
| | 2284 | * |
| | 2285 | * Note that we must store the table of contents in ascending order of |
| | 2286 | * object ID. This happens naturally, since we scan the table in |
| | 2287 | * order of object ID. |
| | 2288 | */ |
| | 2289 | toc_cnt = 0; |
| | 2290 | save_cnt = 0; |
| | 2291 | toc_cnt_pos = fp->get_pos(); |
| | 2292 | fp->write_int4(0); |
| | 2293 | |
| | 2294 | /* now scan the object pages and save the table of contents */ |
| | 2295 | for (id = 0, i = pages_used_, pg = pages_ ; i > 0 ; ++pg, --i) |
| | 2296 | { |
| | 2297 | /* scan all objects on this page */ |
| | 2298 | for (j = VM_OBJ_PAGE_CNT, entry = *pg ; j > 0 ; -- j, ++entry, ++id) |
| | 2299 | { |
| | 2300 | /* |
| | 2301 | * If the entry is currently reachable, and it was dynamically |
| | 2302 | * allocated (which means it's not in the root set), then add |
| | 2303 | * it to the table of contents. Note that we won't |
| | 2304 | * necessarily be saving the object, because the object could |
| | 2305 | * be transient - but if so, then we still want the record of |
| | 2306 | * the transient object, so we'll know on reloading that the |
| | 2307 | * object is no longer available. |
| | 2308 | */ |
| | 2309 | if (!entry->free_ |
| | 2310 | && entry->reachable_ == VMOBJ_REACHABLE |
| | 2311 | && !entry->in_root_set_) |
| | 2312 | { |
| | 2313 | ulong flags; |
| | 2314 | |
| | 2315 | /* set up the flags */ |
| | 2316 | flags = 0; |
| | 2317 | if (entry->transient_) |
| | 2318 | flags |= VMOBJ_TOC_TRANSIENT; |
| | 2319 | |
| | 2320 | /* write the object ID and flags */ |
| | 2321 | fp->write_int4(id); |
| | 2322 | fp->write_int4(flags); |
| | 2323 | |
| | 2324 | /* count it */ |
| | 2325 | ++toc_cnt; |
| | 2326 | } |
| | 2327 | |
| | 2328 | /* if it's saveable, count it among the saveable objects */ |
| | 2329 | if (entry->is_saveable()) |
| | 2330 | ++save_cnt; |
| | 2331 | } |
| | 2332 | } |
| | 2333 | |
| | 2334 | /* go back and fix up the size prefix for the table of contents */ |
| | 2335 | end_pos = fp->get_pos(); |
| | 2336 | fp->set_pos(toc_cnt_pos); |
| | 2337 | fp->write_int4(toc_cnt); |
| | 2338 | fp->set_pos(end_pos); |
| | 2339 | |
| | 2340 | /* write the saveable object count, which we calculated above */ |
| | 2341 | fp->write_int4(save_cnt); |
| | 2342 | |
| | 2343 | /* scan all object pages, and save each reachable object */ |
| | 2344 | for (id = 0, i = pages_used_, pg = pages_ ; i > 0 ; ++pg, --i) |
| | 2345 | { |
| | 2346 | /* scan all objects on this page */ |
| | 2347 | for (j = VM_OBJ_PAGE_CNT, entry = *pg ; j > 0 ; -- j, ++entry, ++id) |
| | 2348 | { |
| | 2349 | /* if this object is saveable, save it */ |
| | 2350 | if (entry->is_saveable()) |
| | 2351 | { |
| | 2352 | uint idx; |
| | 2353 | char buf[2]; |
| | 2354 | |
| | 2355 | /* write the object ID */ |
| | 2356 | fp->write_int4(id); |
| | 2357 | |
| | 2358 | /* store the root-set flag */ |
| | 2359 | buf[0] = (entry->in_root_set_ != 0); |
| | 2360 | |
| | 2361 | /* store the dependency table index */ |
| | 2362 | idx = entry->get_vm_obj()-> |
| | 2363 | get_metaclass_reg()->get_reg_idx(); |
| | 2364 | buf[1] = (char)G_meta_table->get_dependency_index(idx); |
| | 2365 | |
| | 2366 | /* write the data */ |
| | 2367 | fp->write_bytes(buf, 2); |
| | 2368 | |
| | 2369 | /* save the metaclass-specific state */ |
| | 2370 | entry->get_vm_obj()->save_to_file(vmg_ fp); |
| | 2371 | } |
| | 2372 | |
| | 2373 | /* |
| | 2374 | * restore this object to the appropriate conditions in |
| | 2375 | * preparation for the next GC pass, so that we leave things |
| | 2376 | * as we found them -- saving the VM's state thus has no |
| | 2377 | * effect on the VM's state |
| | 2378 | */ |
| | 2379 | gc_set_init_conditions(id, entry); |
| | 2380 | } |
| | 2381 | } |
| | 2382 | } |
| | 2383 | |
| | 2384 | /* |
| | 2385 | * Restore state from a file |
| | 2386 | */ |
| | 2387 | int CVmObjTable::restore(VMG_ CVmFile *fp, CVmObjFixup **fixups) |
| | 2388 | { |
| | 2389 | int err; |
| | 2390 | ulong cnt; |
| | 2391 | |
| | 2392 | /* presume we won't create a fixup table */ |
| | 2393 | *fixups = 0; |
| | 2394 | |
| | 2395 | /* load the metaclass table */ |
| | 2396 | if ((err = G_meta_table->read_from_file(fp)) != 0) |
| | 2397 | return err; |
| | 2398 | |
| | 2399 | /* |
| | 2400 | * Reset all objects to the initial image file load state. Note that |
| | 2401 | * we wait until after we've read the metaclass table to reset the |
| | 2402 | * objects, because any intrinsic class objects in the root set will |
| | 2403 | * need to re-initialize their presence in the metaclass table, which |
| | 2404 | * they can't do until after the metaclass table has itself been |
| | 2405 | * reloaded. |
| | 2406 | */ |
| | 2407 | G_obj_table->reset_to_image(vmg0_); |
| | 2408 | |
| | 2409 | /* read the size of the table of contents */ |
| | 2410 | cnt = fp->read_uint4(); |
| | 2411 | |
| | 2412 | /* allocate the fixup table */ |
| | 2413 | *fixups = new CVmObjFixup(cnt); |
| | 2414 | |
| | 2415 | /* read the fixup table */ |
| | 2416 | for ( ; cnt != 0 ; --cnt) |
| | 2417 | { |
| | 2418 | vm_obj_id_t old_id; |
| | 2419 | vm_obj_id_t new_id; |
| | 2420 | ulong flags; |
| | 2421 | |
| | 2422 | /* read the next entry */ |
| | 2423 | old_id = (vm_obj_id_t)fp->read_uint4(); |
| | 2424 | flags = fp->read_uint4(); |
| | 2425 | |
| | 2426 | /* |
| | 2427 | * Allocate a new object ID for this entry. If the object was |
| | 2428 | * transient, then it won't actually have been saved, so we must |
| | 2429 | * fix up references to the object to nil. |
| | 2430 | */ |
| | 2431 | if (!(flags & VMOBJ_TOC_TRANSIENT)) |
| | 2432 | new_id = vm_new_id(vmg_ FALSE); |
| | 2433 | else |
| | 2434 | new_id = VM_INVALID_OBJ; |
| | 2435 | |
| | 2436 | /* |
| | 2437 | * Add the entry. Note that the table of contents is stored in |
| | 2438 | * ascending order of old ID (i.e., the ID's in the saved state |
| | 2439 | * file's numbering system); this is the same ordering required by |
| | 2440 | * the fixup table, so we can simply read entries from the file |
| | 2441 | * and add them directly to the fixup table. |
| | 2442 | */ |
| | 2443 | (*fixups)->add_fixup(old_id, new_id); |
| | 2444 | } |
| | 2445 | |
| | 2446 | /* read the number of saved objects */ |
| | 2447 | cnt = fp->read_uint4(); |
| | 2448 | |
| | 2449 | /* read the objects */ |
| | 2450 | for ( ; cnt != 0 ; --cnt) |
| | 2451 | { |
| | 2452 | char buf[2]; |
| | 2453 | vm_obj_id_t id; |
| | 2454 | int in_root_set; |
| | 2455 | uint meta_idx; |
| | 2456 | CVmObject *obj; |
| | 2457 | |
| | 2458 | /* read the original object ID */ |
| | 2459 | id = (vm_obj_id_t)fp->read_uint4(); |
| | 2460 | |
| | 2461 | /* read the root-set flag and dependency table index */ |
| | 2462 | fp->read_bytes(buf, 2); |
| | 2463 | in_root_set = buf[0]; |
| | 2464 | meta_idx = (uchar)buf[1]; |
| | 2465 | |
| | 2466 | /* |
| | 2467 | * if it's not in the root set, we need to create a new object; |
| | 2468 | * otherwise, the object must already exist, since it came from |
| | 2469 | * the object file |
| | 2470 | */ |
| | 2471 | if (in_root_set) |
| | 2472 | { |
| | 2473 | /* |
| | 2474 | * make sure the object is valid - since it's supposedly in |
| | 2475 | * the root set, it must already exist |
| | 2476 | */ |
| | 2477 | if (!is_obj_id_valid(id) || get_entry(id)->free_) |
| | 2478 | return VMERR_SAVED_OBJ_ID_INVALID; |
| | 2479 | } |
| | 2480 | else |
| | 2481 | { |
| | 2482 | /* |
| | 2483 | * the object was dynamically allocated, so it will have a new |
| | 2484 | * object number - fix up the object ID to the new numbering |
| | 2485 | * system |
| | 2486 | */ |
| | 2487 | id = (*fixups)->get_new_id(vmg_ id); |
| | 2488 | |
| | 2489 | /* create the object */ |
| | 2490 | G_meta_table->create_for_restore(vmg_ meta_idx, id); |
| | 2491 | } |
| | 2492 | |
| | 2493 | /* read the object's data */ |
| | 2494 | obj = get_obj(id); |
| | 2495 | obj->restore_from_file(vmg_ id, fp, *fixups); |
| | 2496 | } |
| | 2497 | |
| | 2498 | /* success */ |
| | 2499 | return 0; |
| | 2500 | } |
| | 2501 | |
| | 2502 | /* |
| | 2503 | * Save an image data pointer |
| | 2504 | */ |
| | 2505 | void CVmObjTable::save_image_pointer(vm_obj_id_t obj_id, const char *ptr, |
| | 2506 | size_t siz) |
| | 2507 | { |
| | 2508 | vm_image_ptr *slot; |
| | 2509 | |
| | 2510 | /* allocate a new page if we're out of slots on the current page */ |
| | 2511 | if (image_ptr_head_ == 0) |
| | 2512 | { |
| | 2513 | /* no pages yet - allocate the first page */ |
| | 2514 | image_ptr_head_ = (vm_image_ptr_page *) |
| | 2515 | t3malloc(sizeof(vm_image_ptr_page)); |
| | 2516 | if (image_ptr_head_ == 0) |
| | 2517 | err_throw(VMERR_OUT_OF_MEMORY); |
| | 2518 | |
| | 2519 | /* it's also the last page */ |
| | 2520 | image_ptr_tail_ = image_ptr_head_; |
| | 2521 | image_ptr_tail_->next_ = 0; |
| | 2522 | |
| | 2523 | /* no slots used on this page yet */ |
| | 2524 | image_ptr_last_cnt_ = 0; |
| | 2525 | } |
| | 2526 | else if (image_ptr_last_cnt_ == VM_IMAGE_PTRS_PER_PAGE) |
| | 2527 | { |
| | 2528 | /* the last page is full - allocate another one */ |
| | 2529 | image_ptr_tail_->next_ = (vm_image_ptr_page *) |
| | 2530 | t3malloc(sizeof(vm_image_ptr_page)); |
| | 2531 | if (image_ptr_tail_->next_ == 0) |
| | 2532 | err_throw(VMERR_OUT_OF_MEMORY); |
| | 2533 | |
| | 2534 | /* it's the new last page */ |
| | 2535 | image_ptr_tail_ = image_ptr_tail_->next_; |
| | 2536 | image_ptr_tail_->next_ = 0; |
| | 2537 | |
| | 2538 | /* no slots used on this page yet */ |
| | 2539 | image_ptr_last_cnt_ = 0; |
| | 2540 | } |
| | 2541 | |
| | 2542 | /* get the next available slot */ |
| | 2543 | slot = &image_ptr_tail_->ptrs_[image_ptr_last_cnt_]; |
| | 2544 | |
| | 2545 | /* save the data */ |
| | 2546 | slot->obj_id_ = obj_id; |
| | 2547 | slot->image_data_ptr_ = ptr; |
| | 2548 | slot->image_data_len_ = siz; |
| | 2549 | |
| | 2550 | /* count the new record */ |
| | 2551 | ++image_ptr_last_cnt_; |
| | 2552 | } |
| | 2553 | |
| | 2554 | /* |
| | 2555 | * Reset to initial image file state |
| | 2556 | */ |
| | 2557 | void CVmObjTable::reset_to_image(VMG0_) |
| | 2558 | { |
| | 2559 | CVmObjPageEntry **pg; |
| | 2560 | CVmObjPageEntry *entry; |
| | 2561 | size_t i; |
| | 2562 | size_t j; |
| | 2563 | vm_obj_id_t id; |
| | 2564 | vm_image_ptr_page *ip_page; |
| | 2565 | |
| | 2566 | /* |
| | 2567 | * Drop all undo information. Since we're resetting to the initial |
| | 2568 | * state, the undo for our outgoing state will no longer be |
| | 2569 | * relevant. |
| | 2570 | */ |
| | 2571 | G_undo->drop_undo(vmg0_); |
| | 2572 | |
| | 2573 | /* delete all of the globals */ |
| | 2574 | if (globals_ != 0) |
| | 2575 | { |
| | 2576 | delete globals_; |
| | 2577 | globals_ = 0; |
| | 2578 | } |
| | 2579 | |
| | 2580 | /* |
| | 2581 | * Go through the object table and reset each non-transient object in |
| | 2582 | * the root set to its initial conditions. |
| | 2583 | */ |
| | 2584 | for (id = 0, i = pages_used_, pg = pages_ ; i > 0 ; ++pg, --i) |
| | 2585 | { |
| | 2586 | /* scan all objects on this page */ |
| | 2587 | for (j = VM_OBJ_PAGE_CNT, entry = *pg ; j > 0 ; --j, ++entry, ++id) |
| | 2588 | { |
| | 2589 | /* |
| | 2590 | * if it's not free, and it's in the root set, and it's not |
| | 2591 | * transient, reset it |
| | 2592 | */ |
| | 2593 | if (!entry->free_ && entry->in_root_set_ && !entry->transient_) |
| | 2594 | { |
| | 2595 | /* |
| | 2596 | * This object is part of the root set, so it's part of |
| | 2597 | * the state immediately after loading the image. Reset |
| | 2598 | * the object to its load file conditions. |
| | 2599 | */ |
| | 2600 | entry->get_vm_obj()->reset_to_image(vmg_ id); |
| | 2601 | } |
| | 2602 | } |
| | 2603 | } |
| | 2604 | |
| | 2605 | /* |
| | 2606 | * Go through all of the objects for which we've explicitly saved the |
| | 2607 | * original image file location, and ask them to reset using the image |
| | 2608 | * data. |
| | 2609 | */ |
| | 2610 | for (ip_page = image_ptr_head_ ; ip_page != 0 ; ip_page = ip_page->next_) |
| | 2611 | { |
| | 2612 | size_t cnt; |
| | 2613 | vm_image_ptr *slot; |
| | 2614 | |
| | 2615 | /* |
| | 2616 | * get the count for this page - if this is the last page, it's |
| | 2617 | * the last page counter; otherwise, it's a full page, since we |
| | 2618 | * fill up each page before creating a new one |
| | 2619 | */ |
| | 2620 | if (ip_page->next_ == 0) |
| | 2621 | cnt = image_ptr_last_cnt_; |
| | 2622 | else |
| | 2623 | cnt = VM_IMAGE_PTRS_PER_PAGE; |
| | 2624 | |
| | 2625 | /* go through the records on the page */ |
| | 2626 | for (slot = ip_page->ptrs_ ; cnt != 0 ; --cnt, ++slot) |
| | 2627 | { |
| | 2628 | /* it this object is non-transient, reload it */ |
| | 2629 | if (!get_entry(slot->obj_id_)->transient_) |
| | 2630 | { |
| | 2631 | /* reload it using the saved image data */ |
| | 2632 | vm_objp(vmg_ slot->obj_id_) |
| | 2633 | ->reload_from_image(vmg_ slot->obj_id_, |
| | 2634 | slot->image_data_ptr_, |
| | 2635 | slot->image_data_len_); |
| | 2636 | } |
| | 2637 | } |
| | 2638 | } |
| | 2639 | } |
| | 2640 | |
| | 2641 | /* |
| | 2642 | * Create a global variable |
| | 2643 | */ |
| | 2644 | vm_globalvar_t *CVmObjTable::create_global_var() |
| | 2645 | { |
| | 2646 | vm_globalvar_t *var; |
| | 2647 | |
| | 2648 | /* create the new variable */ |
| | 2649 | var = new vm_globalvar_t(); |
| | 2650 | |
| | 2651 | /* initialize the variable's value to nil */ |
| | 2652 | var->val.set_nil(); |
| | 2653 | |
| | 2654 | /* link it into our list of globals */ |
| | 2655 | var->nxt = global_var_head_; |
| | 2656 | var->prv = 0; |
| | 2657 | if (global_var_head_ != 0) |
| | 2658 | global_var_head_->prv = var; |
| | 2659 | global_var_head_ = var; |
| | 2660 | |
| | 2661 | /* return the variable */ |
| | 2662 | return var; |
| | 2663 | } |
| | 2664 | |
| | 2665 | /* |
| | 2666 | * Delete a global variable |
| | 2667 | */ |
| | 2668 | void CVmObjTable::delete_global_var(vm_globalvar_t *var) |
| | 2669 | { |
| | 2670 | /* unlink it from the list of globals */ |
| | 2671 | if (var->nxt != 0) |
| | 2672 | var->nxt->prv = var->prv; |
| | 2673 | |
| | 2674 | if (var->prv != 0) |
| | 2675 | var->prv->nxt = var->nxt; |
| | 2676 | else |
| | 2677 | global_var_head_ = var->nxt; |
| | 2678 | |
| | 2679 | /* delete the memory */ |
| | 2680 | delete var; |
| | 2681 | } |
| | 2682 | |
| | 2683 | |
| | 2684 | /* ------------------------------------------------------------------------ */ |
| | 2685 | /* |
| | 2686 | * post-load initialization status |
| | 2687 | */ |
| | 2688 | enum pli_stat_t |
| | 2689 | { |
| | 2690 | PLI_UNINITED, /* not yet initialized */ |
| | 2691 | PLI_IN_PROGRESS, /* initialization in progress */ |
| | 2692 | PLI_INITED /* initialization completed */ |
| | 2693 | }; |
| | 2694 | |
| | 2695 | /* |
| | 2696 | * Post-load initialization hash table entry. We use the object ID, |
| | 2697 | * treating the binary representation as a string of bytes, as the hash |
| | 2698 | * key. |
| | 2699 | */ |
| | 2700 | class CVmHashEntryPLI: public CVmHashEntryCS |
| | 2701 | { |
| | 2702 | public: |
| | 2703 | CVmHashEntryPLI(vm_obj_id_t id) |
| | 2704 | : CVmHashEntryCS((char *)&id, sizeof(id), TRUE) |
| | 2705 | { |
| | 2706 | /* |
| | 2707 | * remember our object ID for easy access (technically, it's stored |
| | 2708 | * as our key value as well, so this is redundant; but it's |
| | 2709 | * transformed into a block of bytes for the key, so it's easier to |
| | 2710 | * keep a separate copy of the true type) |
| | 2711 | */ |
| | 2712 | id_ = id; |
| | 2713 | |
| | 2714 | /* initialize our status */ |
| | 2715 | status = PLI_UNINITED; |
| | 2716 | } |
| | 2717 | |
| | 2718 | /* our object ID */ |
| | 2719 | vm_obj_id_t id_; |
| | 2720 | |
| | 2721 | /* status for current operation */ |
| | 2722 | pli_stat_t status; |
| | 2723 | }; |
| | 2724 | |
| | 2725 | /* |
| | 2726 | * Request post-load initialization. |
| | 2727 | */ |
| | 2728 | void CVmObjTable::request_post_load_init(vm_obj_id_t obj) |
| | 2729 | { |
| | 2730 | CVmHashEntryPLI *entry; |
| | 2731 | |
| | 2732 | /* check for an existing entry - if there's not one already, add one */ |
| | 2733 | entry = (CVmHashEntryPLI *) |
| | 2734 | post_load_init_table_->find((char *)&obj, sizeof(obj)); |
| | 2735 | if (entry == 0) |
| | 2736 | { |
| | 2737 | /* it's not there yet - add a new entry */ |
| | 2738 | post_load_init_table_->add(new CVmHashEntryPLI(obj)); |
| | 2739 | |
| | 2740 | /* mark the object as having requested post-load initialization */ |
| | 2741 | get_entry(obj)->requested_post_load_init_ = TRUE; |
| | 2742 | } |
| | 2743 | } |
| | 2744 | |
| | 2745 | /* |
| | 2746 | * Explicitly invoke post-load initialization on a given object |
| | 2747 | */ |
| | 2748 | void CVmObjTable::ensure_post_load_init(VMG_ vm_obj_id_t obj) |
| | 2749 | { |
| | 2750 | CVmHashEntryPLI *entry; |
| | 2751 | |
| | 2752 | /* find the entry */ |
| | 2753 | entry = (CVmHashEntryPLI *) |
| | 2754 | post_load_init_table_->find((char *)&obj, sizeof(obj)); |
| | 2755 | |
| | 2756 | /* if we found it, invoke its initialization method */ |
| | 2757 | if (entry != 0) |
| | 2758 | call_post_load_init(vmg_ entry); |
| | 2759 | } |
| | 2760 | |
| | 2761 | /* |
| | 2762 | * Invoke post-load initialization on the given object, if appropriate |
| | 2763 | */ |
| | 2764 | void CVmObjTable::call_post_load_init(VMG_ CVmHashEntryPLI *entry) |
| | 2765 | { |
| | 2766 | /* check the status */ |
| | 2767 | switch (entry->status) |
| | 2768 | { |
| | 2769 | case PLI_UNINITED: |
| | 2770 | /* |
| | 2771 | * It's not yet initialized, so we need to initialize it now. Mark |
| | 2772 | * it as having its initialization in progress. |
| | 2773 | */ |
| | 2774 | entry->status = PLI_IN_PROGRESS; |
| | 2775 | |
| | 2776 | /* |
| | 2777 | * push the entry on the stack to protect it from gc while we're |
| | 2778 | * initializing it |
| | 2779 | */ |
| | 2780 | G_stk->push()->set_obj(entry->id_); |
| | 2781 | |
| | 2782 | /* invoke its initialization */ |
| | 2783 | vm_objp(vmg_ entry->id_)->post_load_init(vmg_ entry->id_); |
| | 2784 | |
| | 2785 | /* mark the object as having completed initialization */ |
| | 2786 | entry->status = PLI_INITED; |
| | 2787 | |
| | 2788 | /* discard our GC protection */ |
| | 2789 | G_stk->discard(); |
| | 2790 | |
| | 2791 | /* done */ |
| | 2792 | break; |
| | 2793 | |
| | 2794 | case PLI_IN_PROGRESS: |
| | 2795 | /* |
| | 2796 | * The object is in the process of being initialized. This must |
| | 2797 | * mean that the object's initializer is calling us indirectly, |
| | 2798 | * probably through another object's initializer that it invoked |
| | 2799 | * explicitly as a dependency, which in turn means that we have a |
| | 2800 | * circular dependency. This is illegal, so abort with an error. |
| | 2801 | */ |
| | 2802 | err_throw(VMERR_CIRCULAR_INIT); |
| | 2803 | break; |
| | 2804 | |
| | 2805 | case PLI_INITED: |
| | 2806 | /* it's already been initialized, so there's nothing to do */ |
| | 2807 | break; |
| | 2808 | } |
| | 2809 | } |
| | 2810 | |
| | 2811 | /* |
| | 2812 | * Remove a post-load initialization record |
| | 2813 | */ |
| | 2814 | void CVmObjTable::remove_post_load_init(vm_obj_id_t obj) |
| | 2815 | { |
| | 2816 | CVmHashEntryPLI *entry; |
| | 2817 | |
| | 2818 | /* find the entry */ |
| | 2819 | entry = (CVmHashEntryPLI *) |
| | 2820 | post_load_init_table_->find((char *)&obj, sizeof(obj)); |
| | 2821 | |
| | 2822 | /* if we found the entry, remove it from the table and delete it */ |
| | 2823 | if (entry != 0) |
| | 2824 | { |
| | 2825 | /* remove it */ |
| | 2826 | post_load_init_table_->remove(entry); |
| | 2827 | |
| | 2828 | /* delete it */ |
| | 2829 | delete entry; |
| | 2830 | |
| | 2831 | /* mark the entry as no longer being registered for post-load init */ |
| | 2832 | get_entry(obj)->requested_post_load_init_ = FALSE; |
| | 2833 | } |
| | 2834 | } |
| | 2835 | |
| | 2836 | /* |
| | 2837 | * post-load initialization enumeration context |
| | 2838 | */ |
| | 2839 | struct pli_enum_ctx |
| | 2840 | { |
| | 2841 | vm_globals *globals; |
| | 2842 | }; |
| | 2843 | |
| | 2844 | /* |
| | 2845 | * Invoke post-load initialization on all objects that have requested it. |
| | 2846 | * This must be called after initial program load, restarts, and restore |
| | 2847 | * operations. |
| | 2848 | */ |
| | 2849 | void CVmObjTable::do_all_post_load_init(VMG0_) |
| | 2850 | { |
| | 2851 | pli_enum_ctx ctx; |
| | 2852 | |
| | 2853 | /* set up our context */ |
| | 2854 | ctx.globals = VMGLOB_ADDR; |
| | 2855 | |
| | 2856 | /* first, mark all entries as having status 'uninitialized' */ |
| | 2857 | post_load_init_table_->enum_entries(&pli_status_cb, &ctx); |
| | 2858 | |
| | 2859 | /* next, invoke the initializer method for each entry */ |
| | 2860 | post_load_init_table_->enum_entries(&pli_invoke_cb, &ctx); |
| | 2861 | } |
| | 2862 | |
| | 2863 | /* |
| | 2864 | * post-load initialization enumeration callback: mark entries as having |
| | 2865 | * status 'uninitialized' |
| | 2866 | */ |
| | 2867 | void CVmObjTable::pli_status_cb(void *ctx0, CVmHashEntry *entry0) |
| | 2868 | { |
| | 2869 | CVmHashEntryPLI *entry = (CVmHashEntryPLI *)entry0; |
| | 2870 | |
| | 2871 | /* mark the entry as having status 'uninitialized' */ |
| | 2872 | entry->status = PLI_UNINITED; |
| | 2873 | } |
| | 2874 | |
| | 2875 | /* |
| | 2876 | * post-load initialization enumeration callback: mark entries as having |
| | 2877 | * status 'uninitialized' |
| | 2878 | */ |
| | 2879 | void CVmObjTable::pli_invoke_cb(void *ctx0, CVmHashEntry *entry0) |
| | 2880 | { |
| | 2881 | pli_enum_ctx *ctx = (pli_enum_ctx *)ctx0; |
| | 2882 | CVmHashEntryPLI *entry = (CVmHashEntryPLI *)entry0; |
| | 2883 | VMGLOB_PTR(ctx->globals); |
| | 2884 | |
| | 2885 | /* invoke post-load initialization on the object */ |
| | 2886 | call_post_load_init(vmg_ entry); |
| | 2887 | } |
| | 2888 | |
| | 2889 | |
| | 2890 | /* ------------------------------------------------------------------------ */ |
| | 2891 | /* |
| | 2892 | * Memory manager implementation |
| | 2893 | */ |
| | 2894 | |
| | 2895 | CVmMemory::CVmMemory(VMG_ CVmVarHeap *varheap) |
| | 2896 | { |
| | 2897 | /* remember our variable-size heap */ |
| | 2898 | varheap_ = varheap; |
| | 2899 | |
| | 2900 | /* initialize the variable-size heap */ |
| | 2901 | varheap_->init(vmg0_); |
| | 2902 | } |
| | 2903 | |
| | 2904 | /* ------------------------------------------------------------------------ */ |
| | 2905 | /* |
| | 2906 | * Hybrid cell/malloc memory manager - cell sub-block manager |
| | 2907 | */ |
| | 2908 | |
| | 2909 | /* |
| | 2910 | * Allocate an object. Since we always allocate blocks of a fixed size, |
| | 2911 | * we can ignore the size parameter; the heap manager will only route us |
| | 2912 | * requests that fit in our blocks. |
| | 2913 | */ |
| | 2914 | CVmVarHeapHybrid_hdr *CVmVarHeapHybrid_head::alloc(size_t) |
| | 2915 | { |
| | 2916 | CVmVarHeapHybrid_hdr *ret; |
| | 2917 | |
| | 2918 | /* if there isn't an entry, allocate another array */ |
| | 2919 | if (first_free_ == 0) |
| | 2920 | { |
| | 2921 | CVmVarHeapHybrid_array *arr; |
| | 2922 | size_t real_cell_size; |
| | 2923 | char *p; |
| | 2924 | size_t cnt; |
| | 2925 | |
| | 2926 | /* |
| | 2927 | * Allocate another page. We need space for the array header |
| | 2928 | * itself, plus space for all of the cells. We want page_count_ |
| | 2929 | * cells, each of size cell_size_ plus the size of the per-cell |
| | 2930 | * header, rounded to the worst-case alignment size for the |
| | 2931 | * platform. |
| | 2932 | */ |
| | 2933 | real_cell_size = osrndsz(cell_size_ |
| | 2934 | + osrndsz(sizeof(CVmVarHeapHybrid_hdr))); |
| | 2935 | arr = (CVmVarHeapHybrid_array *) |
| | 2936 | t3malloc(sizeof(CVmVarHeapHybrid_array) |
| | 2937 | + (page_count_ * real_cell_size)); |
| | 2938 | |
| | 2939 | /* if that failed, throw an error */ |
| | 2940 | if (arr == 0) |
| | 2941 | err_throw(VMERR_OUT_OF_MEMORY); |
| | 2942 | |
| | 2943 | /* link the array into the master list */ |
| | 2944 | arr->next_array = mem_mgr_->first_array_; |
| | 2945 | mem_mgr_->first_array_ = arr; |
| | 2946 | |
| | 2947 | /* |
| | 2948 | * Build the free list. Each cell goes into the free list; the |
| | 2949 | * 'next' pointer is stored in the data area of the cell. |
| | 2950 | */ |
| | 2951 | for (p = arr->mem, cnt = page_count_ ; cnt > 0 ; |
| | 2952 | p += real_cell_size, --cnt) |
| | 2953 | { |
| | 2954 | /* link this one into the free list */ |
| | 2955 | *(void **)p = first_free_; |
| | 2956 | first_free_ = (void *)p; |
| | 2957 | } |
| | 2958 | } |
| | 2959 | |
| | 2960 | /* remember the return value */ |
| | 2961 | ret = (CVmVarHeapHybrid_hdr *)first_free_; |
| | 2962 | |
| | 2963 | /* |
| | 2964 | * when we initialized or last freed this entry, we stored a pointer |
| | 2965 | * to the next item in the free list in the object's data - retrieve |
| | 2966 | * this pointer now, and update our free list head to point to the |
| | 2967 | * next item |
| | 2968 | */ |
| | 2969 | first_free_ = *(void **)first_free_; |
| | 2970 | |
| | 2971 | /* fill in the block's pointer to the allocating heap (i.e., this) */ |
| | 2972 | ret->block = this; |
| | 2973 | |
| | 2974 | /* return the item */ |
| | 2975 | return ret; |
| | 2976 | } |
| | 2977 | |
| | 2978 | /* |
| | 2979 | * Reallocate |
| | 2980 | */ |
| | 2981 | void *CVmVarHeapHybrid_head::realloc(CVmVarHeapHybrid_hdr *mem, size_t siz, |
| | 2982 | CVmObject *obj) |
| | 2983 | { |
| | 2984 | void *new_mem; |
| | 2985 | |
| | 2986 | /* |
| | 2987 | * if the new block fits in our cell size, return the original |
| | 2988 | * memory unchanged; note that we must adjust the pointer so that we |
| | 2989 | * return the client-visible portion |
| | 2990 | */ |
| | 2991 | if (siz <= cell_size_) |
| | 2992 | return (void *)(mem + 1); |
| | 2993 | |
| | 2994 | /* |
| | 2995 | * The memory won't fit in our cell size, so not only can't we |
| | 2996 | * re-use the existing cell, but we can't allocate the memory from |
| | 2997 | * our own sub-block at all. Allocate an entirely new block from |
| | 2998 | * the heap manager. |
| | 2999 | */ |
| | 3000 | new_mem = mem_mgr_->alloc_mem(siz, obj); |
| | 3001 | |
| | 3002 | /* |
| | 3003 | * Copy the old cell's contents to the new memory. Note that the |
| | 3004 | * user-visible portion of the old cell starts immediately after the |
| | 3005 | * header; don't copy the old header, since it's not applicable to |
| | 3006 | * the new object. Note also that we got a pointer directly to the |
| | 3007 | * user-visible portion of the new object, so we don't need to make |
| | 3008 | * any adjustments to the new pointer. |
| | 3009 | */ |
| | 3010 | memcpy(new_mem, (void *)(mem + 1), cell_size_); |
| | 3011 | |
| | 3012 | /* free the old memory */ |
| | 3013 | free(mem); |
| | 3014 | |
| | 3015 | /* return the new memory */ |
| | 3016 | return new_mem; |
| | 3017 | } |
| | 3018 | |
| | 3019 | /* |
| | 3020 | * Release memory |
| | 3021 | */ |
| | 3022 | void CVmVarHeapHybrid_head::free(CVmVarHeapHybrid_hdr *mem) |
| | 3023 | { |
| | 3024 | /* link the block into our free list */ |
| | 3025 | *(void **)mem = first_free_; |
| | 3026 | first_free_ = (void *)mem; |
| | 3027 | } |
| | 3028 | |
| | 3029 | /* ------------------------------------------------------------------------ */ |
| | 3030 | /* |
| | 3031 | * Hybrid cell/malloc heap manager |
| | 3032 | */ |
| | 3033 | |
| | 3034 | /* |
| | 3035 | * construct |
| | 3036 | */ |
| | 3037 | CVmVarHeapHybrid::CVmVarHeapHybrid() |
| | 3038 | { |
| | 3039 | /* set the cell heap count */ |
| | 3040 | cell_heap_cnt_ = 5; |
| | 3041 | |
| | 3042 | /* allocate our cell heap pointer array */ |
| | 3043 | cell_heaps_ = (CVmVarHeapHybrid_head **) |
| | 3044 | t3malloc(cell_heap_cnt_ * sizeof(CVmVarHeapHybrid_head *)); |
| | 3045 | |
| | 3046 | /* if that failed, throw an error */ |
| | 3047 | if (cell_heaps_ == 0) |
| | 3048 | err_throw(VMERR_OUT_OF_MEMORY); |
| | 3049 | |
| | 3050 | /* |
| | 3051 | * Allocate our cell heaps. Set up the heaps so that the pages run |
| | 3052 | * about 32k each. |
| | 3053 | */ |
| | 3054 | cell_heaps_[0] = new CVmVarHeapHybrid_head(this, 32, 850); |
| | 3055 | cell_heaps_[1] = new CVmVarHeapHybrid_head(this, 64, 400); |
| | 3056 | cell_heaps_[2] = new CVmVarHeapHybrid_head(this, 128, 200); |
| | 3057 | cell_heaps_[3] = new CVmVarHeapHybrid_head(this, 256, 100); |
| | 3058 | cell_heaps_[4] = new CVmVarHeapHybrid_head(this, 512, 50); |
| | 3059 | |
| | 3060 | /* allocate our malloc heap manager */ |
| | 3061 | malloc_heap_ = new CVmVarHeapHybrid_malloc(); |
| | 3062 | |
| | 3063 | /* we haven't allocated any cell array pages yet */ |
| | 3064 | first_array_ = 0; |
| | 3065 | } |
| | 3066 | |
| | 3067 | /* |
| | 3068 | * delete |
| | 3069 | */ |
| | 3070 | CVmVarHeapHybrid::~CVmVarHeapHybrid() |
| | 3071 | { |
| | 3072 | size_t i; |
| | 3073 | |
| | 3074 | /* delete our cell heaps */ |
| | 3075 | for (i = 0 ; i < cell_heap_cnt_ ; ++i) |
| | 3076 | delete cell_heaps_[i]; |
| | 3077 | |
| | 3078 | /* delete the cell heap pointer array */ |
| | 3079 | t3free(cell_heaps_); |
| | 3080 | |
| | 3081 | /* delete all of the arrays */ |
| | 3082 | while (first_array_ != 0) |
| | 3083 | { |
| | 3084 | CVmVarHeapHybrid_array *nxt; |
| | 3085 | |
| | 3086 | /* remember the next one */ |
| | 3087 | nxt = first_array_->next_array; |
| | 3088 | |
| | 3089 | /* delete this one */ |
| | 3090 | t3free(first_array_); |
| | 3091 | |
| | 3092 | /* move on to the next one */ |
| | 3093 | first_array_ = nxt; |
| | 3094 | } |
| | 3095 | |
| | 3096 | /* delete the malloc-based subheap manager */ |
| | 3097 | delete malloc_heap_; |
| | 3098 | } |
| | 3099 | |
| | 3100 | /* |
| | 3101 | * allocate memory |
| | 3102 | */ |
| | 3103 | void *CVmVarHeapHybrid::alloc_mem(size_t siz, CVmObject *) |
| | 3104 | { |
| | 3105 | CVmVarHeapHybrid_head **subheap; |
| | 3106 | size_t i; |
| | 3107 | |
| | 3108 | /* scan for a cell-based subheap that can handle the request */ |
| | 3109 | for (i = 0, subheap = cell_heaps_ ; i < cell_heap_cnt_ ; ++i, ++subheap) |
| | 3110 | { |
| | 3111 | /* |
| | 3112 | * If it will fit in this one's cell size, allocate it from this |
| | 3113 | * subheap. Note that we must adjust the return pointer so that |
| | 3114 | * it points to the caller-visible portion of the block returned |
| | 3115 | * from the subheap, which immediately follows the internal |
| | 3116 | * header. |
| | 3117 | */ |
| | 3118 | if (siz <= (*subheap)->get_cell_size()) |
| | 3119 | return (void *)((*subheap)->alloc(siz) + 1); |
| | 3120 | } |
| | 3121 | |
| | 3122 | /* |
| | 3123 | * We couldn't find a cell-based manager that can handle a block |
| | 3124 | * this large. Allocate the block from the default malloc heap. |
| | 3125 | * Note that the caller-visible block is the part that immediately |
| | 3126 | * follows our internal header, so we must adjust the return pointer |
| | 3127 | * accordingly. |
| | 3128 | */ |
| | 3129 | return (void *)(malloc_heap_->alloc(siz) + 1); |
| | 3130 | } |
| | 3131 | |
| | 3132 | /* |
| | 3133 | * reallocate memory |
| | 3134 | */ |
| | 3135 | void *CVmVarHeapHybrid::realloc_mem(size_t siz, void *mem, |
| | 3136 | CVmObject *obj) |
| | 3137 | { |
| | 3138 | CVmVarHeapHybrid_hdr *hdr; |
| | 3139 | |
| | 3140 | /* |
| | 3141 | * get the block header, which immediately precedes the |
| | 3142 | * caller-visible block |
| | 3143 | */ |
| | 3144 | hdr = ((CVmVarHeapHybrid_hdr *)mem) - 1; |
| | 3145 | |
| | 3146 | /* |
| | 3147 | * read the header to get the block manager that originally |
| | 3148 | * allocated the memory, and ask it to reallocate the memory |
| | 3149 | */ |
| | 3150 | return hdr->block->realloc(hdr, siz, obj); |
| | 3151 | } |
| | 3152 | |
| | 3153 | /* |
| | 3154 | * free memory |
| | 3155 | */ |
| | 3156 | void CVmVarHeapHybrid::free_mem(void *mem) |
| | 3157 | { |
| | 3158 | CVmVarHeapHybrid_hdr *hdr; |
| | 3159 | |
| | 3160 | /* |
| | 3161 | * get the block header, which immediately precedes the |
| | 3162 | * caller-visible block |
| | 3163 | */ |
| | 3164 | hdr = ((CVmVarHeapHybrid_hdr *)mem) - 1; |
| | 3165 | |
| | 3166 | /* |
| | 3167 | * read the header to get the block manager that originally |
| | 3168 | * allocated the memory, and ask it to free the memory |
| | 3169 | */ |
| | 3170 | hdr->block->free(hdr); |
| | 3171 | } |
| | 3172 | |