| | 1 | /* $Header: d:/cvsroot/tads/tads3/VMOBJ.H,v 1.3 1999/07/11 00:46:58 MJRoberts Exp $ */ |
| | 2 | |
| | 3 | /* |
| | 4 | * Copyright (c) 1998, 2002 Michael J. Roberts. All Rights Reserved. |
| | 5 | * |
| | 6 | * Please see the accompanying license file, LICENSE.TXT, for information |
| | 7 | * on using and copying this software. |
| | 8 | */ |
| | 9 | /* |
| | 10 | Name |
| | 11 | vmobj.h - VM object memory manager |
| | 12 | Function |
| | 13 | |
| | 14 | Notes |
| | 15 | |
| | 16 | Modified |
| | 17 | 10/20/98 MJRoberts - Creation |
| | 18 | */ |
| | 19 | |
| | 20 | #ifndef VMOBJ_H |
| | 21 | #define VMOBJ_H |
| | 22 | |
| | 23 | #include <stdlib.h> |
| | 24 | #include <memory.h> |
| | 25 | |
| | 26 | #include "vmglob.h" |
| | 27 | #include "vmtype.h" |
| | 28 | #include "vmerr.h" |
| | 29 | #include "vmerrnum.h" |
| | 30 | |
| | 31 | |
| | 32 | /* ------------------------------------------------------------------------ */ |
| | 33 | /* |
| | 34 | * If the VM_REGISTER_METACLASS macro hasn't been defined, define it |
| | 35 | * now. This macro is defined different ways on different inclusions, |
| | 36 | * but when it's not defined, it should always be defined to do nothing. |
| | 37 | */ |
| | 38 | #ifndef VM_REGISTER_METACLASS |
| | 39 | # define VM_REGISTER_METACLASS(metaclass) |
| | 40 | #endif |
| | 41 | |
| | 42 | /* ------------------------------------------------------------------------ */ |
| | 43 | /* |
| | 44 | * NOTE TO METACLASS IMPLEMENTORS - each final concrete class derived |
| | 45 | * from CVmObject that is to be available to VM programs for static |
| | 46 | * image file loading and/or dynamic creation (via the "NEW" |
| | 47 | * instructions) must specify a VM_REGISTER_METACLASS declaration to |
| | 48 | * register the metaclass. This declaration must occur in the |
| | 49 | * metaclass's header file, and must be OUTSIDE of the region protected |
| | 50 | * against multiple inclusion. The definition should look like this: |
| | 51 | * |
| | 52 | * VM_REGISTER_METACLASS("metaclass-string-name", CVmClassName) |
| | 53 | * |
| | 54 | * The string name must be the universally unique metaclass identifier |
| | 55 | * string registered with the T3 VM Specification Maintainer. The class |
| | 56 | * name is the C++ class (derived from CVmObject). |
| | 57 | * |
| | 58 | * Each CVmObject subclass must define certain static construction |
| | 59 | * methods in addition to the virtual methods defined as abstract in |
| | 60 | * CVmObject. See the comments on CVmObject for details. |
| | 61 | */ |
| | 62 | |
| | 63 | |
| | 64 | /* ------------------------------------------------------------------------ */ |
| | 65 | /* |
| | 66 | * To get a pointer to an object (CVmObject *) given an object ID |
| | 67 | * (vm_obj_id_t), use this: |
| | 68 | * |
| | 69 | * CVmObject *obj = vm_objp(vmg_ id); |
| | 70 | * |
| | 71 | * To allocate a new object: |
| | 72 | * |
| | 73 | * vm_obj_id_t id = vm_newid(vmg_ in_root_set); |
| | 74 | *. CVmObject *obj = new (vmg_ id) CVmObjXxx(constructor params); |
| | 75 | * |
| | 76 | * The functions vm_objp() and vm_newid() are defined later in this |
| | 77 | * file. |
| | 78 | */ |
| | 79 | |
| | 80 | |
| | 81 | /* ------------------------------------------------------------------------ */ |
| | 82 | /* |
| | 83 | * Garbage collector work increment. This is the number of objects that |
| | 84 | * the GC will process on each call to gc_pass_queue(). |
| | 85 | * |
| | 86 | * The point of running the GC incrementally is to allow GC work to be |
| | 87 | * interleaved with long-running user I/O operations (such as reading a |
| | 88 | * line of text from the keyboard) in the foreground thread, so the work |
| | 89 | * increment should be chosen so that each call to this routine |
| | 90 | * completes quickly enough that the user will perceive no delay. |
| | 91 | * However, making this number too small will introduce additional |
| | 92 | * overhead by making an excessive number of function calls. |
| | 93 | */ |
| | 94 | const int VM_GC_WORK_INCREMENT = 500; |
| | 95 | |
| | 96 | |
| | 97 | |
| | 98 | /* ------------------------------------------------------------------------ */ |
| | 99 | /* |
| | 100 | * flag values for propDefined |
| | 101 | */ |
| | 102 | #define VMOBJ_PROPDEF_ANY 1 |
| | 103 | #define VMOBJ_PROPDEF_DIRECTLY 2 |
| | 104 | #define VMOBJ_PROPDEF_INHERITS 3 |
| | 105 | #define VMOBJ_PROPDEF_GET_CLASS 4 |
| | 106 | |
| | 107 | |
| | 108 | /* ------------------------------------------------------------------------ */ |
| | 109 | /* |
| | 110 | * Objects are composed of two parts. The first is a fixed-size |
| | 111 | * portion, or header, which consists of an abstract interface (in terms |
| | 112 | * of stored data, this is simply a vtable pointer) and an extension |
| | 113 | * pointer. The extension pointer points to the variable-size second |
| | 114 | * part of the object, which contains all of the additional instance |
| | 115 | * data for the object. |
| | 116 | */ |
| | 117 | |
| | 118 | /* ------------------------------------------------------------------------ */ |
| | 119 | /* |
| | 120 | * |
| | 121 | * The fixed-size parts, or object headers, are allocated from a set of |
| | 122 | * arrays. A master array has a pointer to the sub-arrays, and each |
| | 123 | * sub-array has a fixed number of slots for the fixed-size parts. |
| | 124 | * Thus, it's very fast to find an object header given an object ID. |
| | 125 | * |
| | 126 | * Because each fixed-size part has an abstract interface, we can have |
| | 127 | * multiple implementations for the objects. This would allow us, for |
| | 128 | * example, to include native objects (with an appropriate interface) as |
| | 129 | * though they were normal VM objects. It also allows us to have |
| | 130 | * different types of implementations for VM objects. |
| | 131 | * |
| | 132 | * The fixed-size object parts never move in memory, so we can refer to |
| | 133 | * them with pointers. We can efficiently re-use space as object |
| | 134 | * headers are allocated and deleted, because a new object will always |
| | 135 | * take up exactly the same size as a previous object whose space was |
| | 136 | * freed. |
| | 137 | * |
| | 138 | * The fixed-size objects are always allocated within the page table, |
| | 139 | * thus operator new is overridden to allocate memory within the page |
| | 140 | * table. |
| | 141 | */ |
| | 142 | /* |
| | 143 | * In addition to the virtual methods listed, every object must define a |
| | 144 | * data member as follows: |
| | 145 | * |
| | 146 | * public: static class CVmMetaclass *metaclass_reg_; |
| | 147 | * |
| | 148 | * This must be a static singleton instance of the CVmMetaclass subclass |
| | 149 | * (see below) for the CVmObject subclass. Each CVmObject subclass must |
| | 150 | * have a corresponding CVmMetaclass subclass; the singleton member |
| | 151 | * variable metaclass_reg_ provides the registration table entry that |
| | 152 | * allows instances of this object to be dynamically linked from the |
| | 153 | * image file. |
| | 154 | */ |
| | 155 | class CVmObject |
| | 156 | { |
| | 157 | friend class CVmVarHeap; |
| | 158 | |
| | 159 | public: |
| | 160 | /* metaclass registration object (for the root object implementation) */ |
| | 161 | static class CVmMetaclass *metaclass_reg_; |
| | 162 | |
| | 163 | /* |
| | 164 | * Default implementation for calling a static property. We don't |
| | 165 | * have any static properties at this level, so we'll simply return |
| | 166 | * false to indicate that the property wasn't evaluated. |
| | 167 | */ |
| | 168 | static int call_stat_prop(VMG_ vm_val_t *retval, const uchar **pc_ptr, |
| | 169 | uint *argc, vm_prop_id_t); |
| | 170 | |
| | 171 | /* get the registration object for this metaclass */ |
| | 172 | virtual class CVmMetaclass *get_metaclass_reg() const |
| | 173 | { return metaclass_reg_; } |
| | 174 | |
| | 175 | /* |
| | 176 | * Is this object of the given metaclass? Returns true if the |
| | 177 | * object is an instance of 'meta' or inherits from the metaclass. |
| | 178 | * Each object class must override this. |
| | 179 | */ |
| | 180 | virtual int is_of_metaclass(class CVmMetaclass *meta) const |
| | 181 | { return (meta == metaclass_reg_); } |
| | 182 | |
| | 183 | /* |
| | 184 | * Receive notification that this object is being deleted - the |
| | 185 | * garbage collector calls this function when the object is |
| | 186 | * unreachable. |
| | 187 | * |
| | 188 | * Note that we don't use the real destructor, since we use our own |
| | 189 | * memory management; instead, we have this virtual finalizer that |
| | 190 | * we explicitly call when it's time to delete the object. (This |
| | 191 | * isn't entirely symmetrical with the overridden operator new, but |
| | 192 | * the GC is the only code that can delete objects, and this saves |
| | 193 | * us the trouble of overriding operator delete for the object.) |
| | 194 | */ |
| | 195 | virtual void notify_delete(VMG_ int in_root_set) = 0; |
| | 196 | |
| | 197 | /* |
| | 198 | * Create an instance of this class. If this object does not |
| | 199 | * represent a class, or cannot be instanced, throw an error. By |
| | 200 | * default, objects cannot be instanced, so we'll simply throw an |
| | 201 | * error. If successful, leaves the new object in register R0. |
| | 202 | * |
| | 203 | * Parameters to the constructor are passed on the VM stack; 'argc' |
| | 204 | * gives the number of arguments on the stack. This routine will |
| | 205 | * remove the arguments from the stack before returning. |
| | 206 | */ |
| | 207 | virtual void create_instance(VMG_ vm_obj_id_t self, |
| | 208 | const uchar **pc_ptr, uint argc) |
| | 209 | { |
| | 210 | /* throw the error */ |
| | 211 | err_throw(VMERR_CANNOT_CREATE_INST); |
| | 212 | } |
| | 213 | |
| | 214 | /* |
| | 215 | * Determine if the object has a non-trivial finalizer. Returns |
| | 216 | * true if the object has a non-trivial finalizer, false if it has |
| | 217 | * no finalizer or the finalizer is trivial and hence can be |
| | 218 | * ignored. We'll return false by default. |
| | 219 | */ |
| | 220 | virtual int has_finalizer(VMG_ vm_obj_id_t /*self*/) |
| | 221 | { return FALSE; } |
| | 222 | |
| | 223 | /* |
| | 224 | * Invoke the object's finalizer. This need do nothing if the |
| | 225 | * object does not define or inherit a finalizer method. Any |
| | 226 | * exceptions thrown in the course of executing the finalizer should |
| | 227 | * be caught and discarded. By default, we'll do nothing at all. |
| | 228 | */ |
| | 229 | virtual void invoke_finalizer(VMG_ vm_obj_id_t /*self*/) { } |
| | 230 | |
| | 231 | /* |
| | 232 | * Determine if this is a class object. This returns true if this |
| | 233 | * object is a class, false if it's an instance. |
| | 234 | */ |
| | 235 | virtual int is_class_object(VMG_ vm_obj_id_t /*self*/) const |
| | 236 | { return FALSE; } |
| | 237 | |
| | 238 | /* |
| | 239 | * Determine if this object is an instance of another object. |
| | 240 | * Returns true if this object derives from the other object, |
| | 241 | * directly or indirectly. If this object derives from an object |
| | 242 | * which in turn derives from the given object, then this object |
| | 243 | * derives (indirectly) from the given object. |
| | 244 | */ |
| | 245 | virtual int is_instance_of(VMG_ vm_obj_id_t obj); |
| | 246 | |
| | 247 | /* |
| | 248 | * Get the number of superclasses of the object, and get the nth |
| | 249 | * superclass. By default, we have one superclass, which is the |
| | 250 | * IntrinsicClass object that represents this metaclass. |
| | 251 | */ |
| | 252 | virtual int get_superclass_count(VMG_ vm_obj_id_t /*self*/) const |
| | 253 | { return 1; } |
| | 254 | virtual vm_obj_id_t get_superclass(VMG_ vm_obj_id_t /*self*/, |
| | 255 | int /*superclass_index*/) const; |
| | 256 | |
| | 257 | /* |
| | 258 | * Determine if the object has properties that can be enumerated. |
| | 259 | * Returns true if so, false if not. Note that this should return |
| | 260 | * true for an object of a type that provides properties, even if |
| | 261 | * the instance happens to have zero properties. |
| | 262 | */ |
| | 263 | virtual int provides_props(VMG0_) const { return FALSE; } |
| | 264 | |
| | 265 | /* |
| | 266 | * Enumerate properties of the object. Invoke the callback for each |
| | 267 | * property. The callback is not permitted to make any changes to |
| | 268 | * the object or its properties and should not invoke garbage |
| | 269 | * collection. |
| | 270 | */ |
| | 271 | virtual void enum_props(VMG_ vm_obj_id_t self, |
| | 272 | void (*cb)(VMG_ void *ctx, |
| | 273 | vm_obj_id_t self, vm_prop_id_t prop, |
| | 274 | const vm_val_t *val), |
| | 275 | void *cbctx) |
| | 276 | { |
| | 277 | /* by default, assume we have nothing to enumerate */ |
| | 278 | } |
| | 279 | |
| | 280 | /* set a property value */ |
| | 281 | virtual void set_prop(VMG_ class CVmUndo *undo, |
| | 282 | vm_obj_id_t self, vm_prop_id_t prop, |
| | 283 | const vm_val_t *val) = 0; |
| | 284 | |
| | 285 | /* |
| | 286 | * Get a property value. We do not evaluate the property, but |
| | 287 | * merely get the raw value; thus, if the property value is of type |
| | 288 | * code, we simply retrieve the code offset pointer. Returns true |
| | 289 | * if the property was found, false if not. |
| | 290 | * |
| | 291 | * If we find the object, we'll set *source_obj to the ID of the |
| | 292 | * object in which we found the object. If the property was |
| | 293 | * supplied by this object directly, we'll simply set source_id to |
| | 294 | * self; if we inherited the property from a superclass, we'll set |
| | 295 | * *source_id to the ID of the superclass object that actually |
| | 296 | * supplied the value. |
| | 297 | * |
| | 298 | * If 'argc' is not null, this function can consume arguments from |
| | 299 | * the run-time stack, and set *argc to zero to indicate that the |
| | 300 | * arguments have been consumed. |
| | 301 | * |
| | 302 | * If 'argc' is null, and evaluating the property would involve |
| | 303 | * running system code (with or without consuming arguments), this |
| | 304 | * function should return TRUE, but should NOT run the system code - |
| | 305 | * instead, set val->typ to VM_NATIVE_CODE to indicate that the |
| | 306 | * value is a "native code" value (i.e., evaluating it requires |
| | 307 | * executing system code). |
| | 308 | */ |
| | 309 | virtual int get_prop(VMG_ vm_prop_id_t prop, vm_val_t *val, |
| | 310 | vm_obj_id_t self, vm_obj_id_t *source_obj, |
| | 311 | uint *argc); |
| | 312 | |
| | 313 | /* |
| | 314 | * Inherit a property value. This works similarly to get_prop, but |
| | 315 | * finds an inherited definition of the property, as though |
| | 316 | * orig_target_obj.prop (and anything overriding orig_target_obj.prop) |
| | 317 | * were undefined. |
| | 318 | * |
| | 319 | * In broad terms, the algorithm for this method is to do the same |
| | 320 | * thing as get_prop(), but to ignore every definition of the property |
| | 321 | * found in the class tree until after reaching and skipping |
| | 322 | * orig_target_obj.prop. Once orig_target_obj.prop is found, this |
| | 323 | * method simply continues searching in the same manner as get_prop() |
| | 324 | * and returns the next definition it finds. |
| | 325 | * |
| | 326 | * 'defining_obj' is the object containing the method currently |
| | 327 | * running. This is not necessarily 'self', because the method |
| | 328 | * currently running might already have been inherited from a |
| | 329 | * superclass of 'self'. |
| | 330 | * |
| | 331 | * 'orig_target_obj' is the object that was originally targeted for the |
| | 332 | * get_prop() operation that invoked the calling method (or invoked the |
| | 333 | * method that inherited the calling method, or so on). This gives us |
| | 334 | * the starting point in the search, so that we can continue the |
| | 335 | * original inheritance tree search that started with get_prop(). |
| | 336 | * |
| | 337 | * 'argc' has the same meaning as for get_prop(). |
| | 338 | * |
| | 339 | * Objects that cannot be subclassed via byte-code can ignore this |
| | 340 | * method. The base class implementation follows the inheritance chain |
| | 341 | * of "modifier" objects, which allow byte-code methods to be plugged |
| | 342 | * in under the native code class tree. |
| | 343 | */ |
| | 344 | virtual int inh_prop(VMG_ vm_prop_id_t prop, vm_val_t *retval, |
| | 345 | vm_obj_id_t self, vm_obj_id_t orig_target_obj, |
| | 346 | vm_obj_id_t defining_obj, vm_obj_id_t *source_obj, |
| | 347 | uint *argc); |
| | 348 | |
| | 349 | /* |
| | 350 | * Build a list of the properties directly defined on this object |
| | 351 | * instance. On return, retval must be filled in with the new list |
| | 352 | * object. |
| | 353 | * |
| | 354 | * Note that a self-reference must be pushed by the caller to protect |
| | 355 | * against garbage collection of self while this routine is running. |
| | 356 | * |
| | 357 | * Most object types do not define any properties in the instance, so |
| | 358 | * this default implementation will simply return an empty list. |
| | 359 | * Classes that can define properties in instances (such as |
| | 360 | * TadsObjects), and the "intrinsic class" class that represents |
| | 361 | * classes, must override this to build their lists. |
| | 362 | */ |
| | 363 | virtual void build_prop_list(VMG_ vm_obj_id_t self, vm_val_t *retval); |
| | 364 | |
| | 365 | /* |
| | 366 | * Mark all strongly-referenced objects. Calls |
| | 367 | * obj_table->mark_refs() for each referenced object. |
| | 368 | */ |
| | 369 | virtual void mark_refs(VMG_ uint state) = 0; |
| | 370 | |
| | 371 | /* |
| | 372 | * Remove stale weak references. For each weakly-referenced object, |
| | 373 | * check to see if the object is marked as reachable; if not, it's |
| | 374 | * about to be deleted, so forget the weak reference. |
| | 375 | */ |
| | 376 | virtual void remove_stale_weak_refs(VMG0_) = 0; |
| | 377 | |
| | 378 | /* |
| | 379 | * Receive notification that the undo manager is creating a new |
| | 380 | * savepoint. |
| | 381 | */ |
| | 382 | virtual void notify_new_savept() = 0; |
| | 383 | |
| | 384 | /* |
| | 385 | * apply an undo record created by this object; if the record has |
| | 386 | * any additional data associated with it (allocated by the object |
| | 387 | * when the undo record was created), this should also discard the |
| | 388 | * additional data |
| | 389 | */ |
| | 390 | virtual void apply_undo(VMG_ struct CVmUndoRecord *rec) = 0; |
| | 391 | |
| | 392 | /* |
| | 393 | * Discard any extra information associated with this undo record. |
| | 394 | * Note that this will not be called if apply_undo() is called, |
| | 395 | * since apply_undo() is expected to discard any extra information |
| | 396 | * itself after applying the record. |
| | 397 | */ |
| | 398 | virtual void discard_undo(VMG_ struct CVmUndoRecord *) { } |
| | 399 | |
| | 400 | /* |
| | 401 | * Mark an object object reference. If this object keeps strong |
| | 402 | * references, this should mark any object contained in the undo |
| | 403 | * record's saved value as referenced; if this object keeps only |
| | 404 | * weak references, this doesn't need to do anything. |
| | 405 | */ |
| | 406 | virtual void mark_undo_ref(VMG_ struct CVmUndoRecord *rec) = 0; |
| | 407 | |
| | 408 | /* |
| | 409 | * Remove any stale weak reference contained in an undo record. For |
| | 410 | * objects with ordinary strong references, this doesn't need to do |
| | 411 | * anything. For objects that keep weak references to other |
| | 412 | * objects, this should check the object referenced in the undo |
| | 413 | * record, if any, to determine if the object is about to be |
| | 414 | * deleted, and if so clear the undo record (by setting the object |
| | 415 | * in the old value to 'invalid'). |
| | 416 | */ |
| | 417 | virtual void remove_stale_undo_weak_ref(VMG_ |
| | 418 | struct CVmUndoRecord *rec) = 0; |
| | 419 | |
| | 420 | /* |
| | 421 | * Post-load initialization. This routine is called only if the object |
| | 422 | * specifically requests this by calling request_post_load_init(). |
| | 423 | * This routine is called exactly once for each initial program load, |
| | 424 | * restart, or restore, and is called after ALL objects are loaded. |
| | 425 | * |
| | 426 | * The purpose of this routine is to allow an object to perform |
| | 427 | * initializations that depend upon other objects. During the normal |
| | 428 | * load/restore/reset methods, an object cannot assume that any other |
| | 429 | * objects are already loaded, because the order of loading is |
| | 430 | * arbitrary. When this routine is called, it is guaranteed that all |
| | 431 | * objects are already loaded. |
| | 432 | */ |
| | 433 | virtual void post_load_init(VMG_ vm_obj_id_t /*self*/) |
| | 434 | { |
| | 435 | /* we do nothing by default */ |
| | 436 | } |
| | 437 | |
| | 438 | /* |
| | 439 | * Load the object from an image file. The object's data block is |
| | 440 | * at the given address and has the given size. |
| | 441 | * |
| | 442 | * The underlying memory is owned by the image file loader. The |
| | 443 | * object must not attempt to deallocate this memory. |
| | 444 | */ |
| | 445 | virtual void load_from_image(VMG_ vm_obj_id_t self, |
| | 446 | const char *ptr, size_t siz) = 0; |
| | 447 | |
| | 448 | /* |
| | 449 | * Reload the object from an image file. The object's data block is |
| | 450 | * at the given address and has the given size. Discards any changes |
| | 451 | * made since the object was loaded and restores its state as it was |
| | 452 | * immediately after it was loaded from the image file. By default, |
| | 453 | * we do nothing. |
| | 454 | * |
| | 455 | * NOTE 1: this routine can be implemented instead of |
| | 456 | * reset_to_image(). If an object doesn't have any other need to |
| | 457 | * store a pointer to its image file data in its own extension, but |
| | 458 | * the image file data is necessary to effect a reset, then this |
| | 459 | * routine should be used, so as to avoid having to enlarge the |
| | 460 | * object's extension for non-image instances. |
| | 461 | * |
| | 462 | * NOTE 2: in order to use this routine, the object MUST call the |
| | 463 | * object table's save_image_pointer() routine during the initial |
| | 464 | * loading (i.e., in the object's load_from_image()). During a reset, |
| | 465 | * the object table will only call this routine on objects whose image |
| | 466 | * pointers were previously saved with save_image_pointer(). |
| | 467 | */ |
| | 468 | virtual void reload_from_image(VMG_ vm_obj_id_t self, |
| | 469 | const char *ptr, size_t siz) { } |
| | 470 | |
| | 471 | /* |
| | 472 | * Reset to the image file state. Discards any changes made since the |
| | 473 | * object was loaded and restores its state as it was immediately |
| | 474 | * after it was loaded from the image file. By default, we do nothing. |
| | 475 | * |
| | 476 | * NOTE: this routine doesn't have to do anything if |
| | 477 | * reload_from_image() is implemented for the object. |
| | 478 | */ |
| | 479 | virtual void reset_to_image(VMG_ vm_obj_id_t /*self*/) { } |
| | 480 | |
| | 481 | /* |
| | 482 | * Determine if the object has been changed since it was loaded from |
| | 483 | * the image file. This can only be called for objects that were |
| | 484 | * originally loaded from the image file. Returns true if the |
| | 485 | * object's internal state has been changed since loading, false if |
| | 486 | * the object is in exactly the same state that's stored in the |
| | 487 | * image file. |
| | 488 | * |
| | 489 | * If this returns false, then it is not necessary to save the |
| | 490 | * object to a saved state file, because the object's state can be |
| | 491 | * restored simply by resetting to the image file state. |
| | 492 | */ |
| | 493 | virtual int is_changed_since_load() const { return FALSE; } |
| | 494 | |
| | 495 | /* |
| | 496 | * save this object to a file, so that it can be restored to its |
| | 497 | * current state via restore_from_file |
| | 498 | */ |
| | 499 | virtual void save_to_file(VMG_ class CVmFile *fp) = 0; |
| | 500 | |
| | 501 | /* |
| | 502 | * Restore the state of the object from a file into which state was |
| | 503 | * previously stored via save_to_file. This routine may need |
| | 504 | * access to the memory manager because it may need to allocate |
| | 505 | * memory to make room for the variable data. |
| | 506 | */ |
| | 507 | virtual void restore_from_file(VMG_ vm_obj_id_t self, |
| | 508 | class CVmFile *fp, |
| | 509 | class CVmObjFixup *fixups) = 0; |
| | 510 | |
| | 511 | /* |
| | 512 | * Compare to another value for equality. Returns true if the value is |
| | 513 | * equal, false if not. By default, we return true only if the other |
| | 514 | * value is an object with the same ID (i.e., a reference to exactly |
| | 515 | * the same instance). However, subclasses can override this to |
| | 516 | * provide different behavior; the string class, for example, may |
| | 517 | * override this so that it compares equal to any string object or |
| | 518 | * constant containing the same string text. |
| | 519 | * |
| | 520 | * 'depth' has the same meaning as in calc_hash(). |
| | 521 | */ |
| | 522 | virtual int equals(VMG_ vm_obj_id_t self, const vm_val_t *val, |
| | 523 | int /*depth*/) const |
| | 524 | { |
| | 525 | /* return true if the other value is a reference to this object */ |
| | 526 | return (val->typ == VM_OBJ && val->val.obj == self); |
| | 527 | } |
| | 528 | |
| | 529 | /* |
| | 530 | * Compare magnitude of this object and another object. Returns a |
| | 531 | * positive value if this object is greater than the other object, a |
| | 532 | * negative value if this object is less than the other object, or |
| | 533 | * zero if this object equals the other object. Throws an error if |
| | 534 | * a magnitude comparison is not meaningful between the two objects. |
| | 535 | * |
| | 536 | * By default, magnitude comparisons between objects are not |
| | 537 | * meaningful, so we throw an error. |
| | 538 | */ |
| | 539 | virtual int compare_to(VMG_ vm_obj_id_t /*self*/, const vm_val_t *) const |
| | 540 | { |
| | 541 | /* by default, magnitude comparisons between objects are illegal */ |
| | 542 | err_throw(VMERR_INVALID_COMPARISON); |
| | 543 | |
| | 544 | /* the compiler doesn't know that we'll never get here */ |
| | 545 | AFTER_ERR_THROW(return 0;) |
| | 546 | } |
| | 547 | |
| | 548 | /* |
| | 549 | * Calculate a hash value for the object. |
| | 550 | * |
| | 551 | * 'depth' is the recursion depth of the hash calculation. Objects |
| | 552 | * that can potentially contain cyclical references back to themselves, |
| | 553 | * and which follow those references to calculate the hash value |
| | 554 | * recursively, must check the depth counter to see if it exceeds |
| | 555 | * VM_MAX_TREE_DEPTH_EQ, throwing VMERR_TREE_TOO_DEEP_EQ if so; and |
| | 556 | * they must increment the depth counter in the recursive traversals. |
| | 557 | * Objects that don't traverse into their contents can ignore the depth |
| | 558 | * counter. |
| | 559 | */ |
| | 560 | virtual uint calc_hash(VMG_ vm_obj_id_t self, int /*depth*/) const |
| | 561 | { |
| | 562 | /* |
| | 563 | * by default, use a 16-bit hash of our object ID as the hash |
| | 564 | * value |
| | 565 | */ |
| | 566 | return (uint)(((ulong)self & 0xffff) |
| | 567 | ^ (((ulong)self & 0xffff0000) >> 16)); |
| | 568 | } |
| | 569 | |
| | 570 | |
| | 571 | /* |
| | 572 | * Add a value to this object, returning the result in *result. |
| | 573 | * This may create a new object to hold the result, or may modify |
| | 574 | * the existing object in place, depending on the subclass |
| | 575 | * implementation. 'self' is the object ID of this object. |
| | 576 | * |
| | 577 | * By default, we'll throw an error indicating that the value cannot |
| | 578 | * be added. |
| | 579 | */ |
| | 580 | virtual void add_val(VMG_ vm_val_t * /*result*/, |
| | 581 | vm_obj_id_t /*self*/, const vm_val_t * /*val*/) |
| | 582 | { |
| | 583 | /* throw an error */ |
| | 584 | err_throw(VMERR_BAD_TYPE_ADD); |
| | 585 | } |
| | 586 | |
| | 587 | /* |
| | 588 | * Subtract a value from this object, returning the result in |
| | 589 | * *result. This may create a new object to hold the result, or may |
| | 590 | * modify the existing object in place, depending upon the subclass |
| | 591 | * implementation. 'self' is the object ID of this object. |
| | 592 | */ |
| | 593 | virtual void sub_val(VMG_ vm_val_t * /*result*/, |
| | 594 | vm_obj_id_t /*self*/, const vm_val_t * /*val*/) |
| | 595 | { |
| | 596 | /* throw an error */ |
| | 597 | err_throw(VMERR_BAD_TYPE_SUB); |
| | 598 | } |
| | 599 | |
| | 600 | /* multiply this object by a value, returning the result in *result */ |
| | 601 | virtual void mul_val(VMG_ vm_val_t * /* result*/, |
| | 602 | vm_obj_id_t /*self*/, const vm_val_t * /*val*/) |
| | 603 | { |
| | 604 | /* throw an error */ |
| | 605 | err_throw(VMERR_BAD_TYPE_MUL); |
| | 606 | } |
| | 607 | |
| | 608 | /* divide a value into this object, returning the result in *result */ |
| | 609 | virtual void div_val(VMG_ vm_val_t * /* result*/, |
| | 610 | vm_obj_id_t /*self*/, const vm_val_t * /*val*/) |
| | 611 | { |
| | 612 | /* throw an error */ |
| | 613 | err_throw(VMERR_BAD_TYPE_DIV); |
| | 614 | } |
| | 615 | |
| | 616 | /* negate the value, returning the result in *result */ |
| | 617 | virtual void neg_val(VMG_ vm_val_t * /* result */, vm_obj_id_t self) |
| | 618 | { |
| | 619 | /* throw an error */ |
| | 620 | err_throw(VMERR_BAD_TYPE_NEG); |
| | 621 | } |
| | 622 | |
| | 623 | /* |
| | 624 | * Get the effective number of values this value contributes when |
| | 625 | * used as the right-hand side of a '+' or '-' operator with a |
| | 626 | * left-hand type that treats these operators as concatenation/set |
| | 627 | * subtraction operators. By default, a value adds/subtracts only |
| | 628 | * itself, but certain collection types (List, Vector, Array) |
| | 629 | * add/subtract their elements individually. |
| | 630 | */ |
| | 631 | virtual size_t get_coll_addsub_rhs_ele_cnt(VMG0_) const |
| | 632 | { |
| | 633 | /* by default, we contribute only 'self', thus only one element */ |
| | 634 | return 1; |
| | 635 | } |
| | 636 | |
| | 637 | /* get the nth element as the rhs of a collection add/subtract */ |
| | 638 | virtual void get_coll_addsub_rhs_ele(VMG_ vm_val_t *retval, |
| | 639 | vm_obj_id_t self, size_t /*idx*/) |
| | 640 | { |
| | 641 | /* by default, we contribute only 'self' */ |
| | 642 | retval->set_obj(self); |
| | 643 | } |
| | 644 | |
| | 645 | /* |
| | 646 | * Index the object. Most object types cannot be indexed, so by |
| | 647 | * default we'll throw an error. Subclasses that can be indexed |
| | 648 | * (such as lists) should look up the element given by the index |
| | 649 | * value, and store the value at that index in *result. |
| | 650 | */ |
| | 651 | virtual void index_val(VMG_ vm_val_t * /*result*/, |
| | 652 | vm_obj_id_t /*self*/, |
| | 653 | const vm_val_t * /*index_val*/) |
| | 654 | { |
| | 655 | /* by default, throw an error */ |
| | 656 | err_throw(VMERR_CANNOT_INDEX_TYPE); |
| | 657 | } |
| | 658 | |
| | 659 | /* |
| | 660 | * Set an indexed element of the object, and fill in *new_container |
| | 661 | * with the new object that results if an entire new object is |
| | 662 | * created to hold the modified contents, or with the original |
| | 663 | * object if the contents of the original object are actually |
| | 664 | * modified by this operation. Lists, for example, cannot be |
| | 665 | * modified, so always create a new list object when an element is |
| | 666 | * set; arrays, in contrast, can be directly modified, so will |
| | 667 | * simply return 'self' in *new_container. |
| | 668 | * |
| | 669 | * By default, we'll throw an error, since default objects cannot be |
| | 670 | * indexed. |
| | 671 | */ |
| | 672 | virtual void set_index_val(VMG_ vm_val_t * /*new_container*/, |
| | 673 | vm_obj_id_t /*self*/, |
| | 674 | const vm_val_t * /*index_val*/, |
| | 675 | const vm_val_t * /*new_val*/) |
| | 676 | { |
| | 677 | /* by default, throw an error */ |
| | 678 | err_throw(VMERR_CANNOT_INDEX_TYPE); |
| | 679 | } |
| | 680 | |
| | 681 | /* |
| | 682 | * Get a string representation of the object. If necessary, this |
| | 683 | * can create a new string object to represent the result. |
| | 684 | * |
| | 685 | * Returns the string representation in portable string format: the |
| | 686 | * first two bytes give the byte length of the rest of the string in |
| | 687 | * portable UINT2 format, and immediately following the length |
| | 688 | * prefix are the bytes of the string's contents. The length prefix |
| | 689 | * does not count the length prefix itself in the length of the |
| | 690 | * string. |
| | 691 | * |
| | 692 | * If a new string object is created, new_str must be set to a |
| | 693 | * reference to the new string object. If a pointer into our data |
| | 694 | * is returned, we must set new_str to self. If no object data are |
| | 695 | * involved, new_str can be set to nil. |
| | 696 | * |
| | 697 | * If it is not possible to create a string representation of the |
| | 698 | * object, throw an error (VMERR_NO_STR_CONV). |
| | 699 | * |
| | 700 | * By default, we'll throw an error indicating that the object |
| | 701 | * cannot be converted to a string. |
| | 702 | */ |
| | 703 | virtual const char *cast_to_string(VMG_ vm_obj_id_t /*self*/, |
| | 704 | vm_val_t * /*new_str*/) const |
| | 705 | { |
| | 706 | /* throw an error */ |
| | 707 | err_throw(VMERR_NO_STR_CONV); |
| | 708 | |
| | 709 | /* we can't get here, but the compiler doesn't know that */ |
| | 710 | AFTER_ERR_THROW(return 0;) |
| | 711 | } |
| | 712 | |
| | 713 | /* |
| | 714 | * Get the list contained in the object, if possible, or null if |
| | 715 | * not. If the value contained in the object is a list, this |
| | 716 | * returns a pointer to the list value in portable format (the first |
| | 717 | * two bytes are the length prefix as a portable UINT2, and the |
| | 718 | * subsequent bytes form a packed array of data holders in portable |
| | 719 | * format). |
| | 720 | * |
| | 721 | * Most object classes will return null for this, since they do not |
| | 722 | * store lists. By default, we return null. |
| | 723 | */ |
| | 724 | virtual const char *get_as_list() const { return 0; } |
| | 725 | |
| | 726 | /* |
| | 727 | * Get the string contained in the object, if possible, or null if |
| | 728 | * not. If the value contained in the object is a string, this |
| | 729 | * returns a pointer to the string value in portable format (the |
| | 730 | * first two bytes are the string prefix as a portable UINT2, and |
| | 731 | * the bytes of the string's text immediately follow in UTF8 format). |
| | 732 | * |
| | 733 | * Most object classes will return null for this, since they do not |
| | 734 | * store strings. By default, we return null. |
| | 735 | */ |
| | 736 | virtual const char *get_as_string(VMG0_) const { return 0; } |
| | 737 | |
| | 738 | /* |
| | 739 | * Rebuild the image data for the object. This is used to write the |
| | 740 | * program state to a new image file after executing the program for |
| | 741 | * a while, such as after a 'preinit' step after compilation. |
| | 742 | * |
| | 743 | * This should write the complete metaclass-specific record needed |
| | 744 | * for an OBJS data block entry, not including the generic header. |
| | 745 | * |
| | 746 | * On success, return the size in bytes of the data stored to the |
| | 747 | * buffer; these bytes must be copied directly to the new image |
| | 748 | * file. If the given buffer isn't big enough, this should return |
| | 749 | * the size needed and otherwise leave the buffer empty. If the |
| | 750 | * object doesn't need to be written at all, this should return |
| | 751 | * zero. |
| | 752 | */ |
| | 753 | virtual ulong rebuild_image(VMG_ char *, ulong) |
| | 754 | { return 0; } |
| | 755 | |
| | 756 | /* |
| | 757 | * Allocate space for conversion to constant data for storage in a |
| | 758 | * rebuilt image file. If this object can be stored as constant |
| | 759 | * data in a rebuilt image file, this should reserve space in the |
| | 760 | * provided constant mapper object for the object's data in |
| | 761 | * preparation for conversion. |
| | 762 | * |
| | 763 | * Most objects cannot be converted to constant data, so the default |
| | 764 | * here does nothing. Those that can, such as strings and lists, |
| | 765 | * should override this. |
| | 766 | */ |
| | 767 | virtual void reserve_const_data(VMG_ class CVmConstMapper *, |
| | 768 | vm_obj_id_t /*self*/) |
| | 769 | { /* default does nothing */ } |
| | 770 | |
| | 771 | /* |
| | 772 | * Convert to constant data. If this object can be stored as |
| | 773 | * constant data in a rebuilt image file, it should override this |
| | 774 | * routine to store its data in the provided constant mapper object. |
| | 775 | * If this object makes reference to other objects, it must check |
| | 776 | * each object that it references to determine if the object has a |
| | 777 | * non-zero address in the mapper, and if so must convert its |
| | 778 | * reference to the object to a constant data item at the given |
| | 779 | * address. There is no default implementation of this routine, |
| | 780 | * because it must be overridden by essentially all objects (at |
| | 781 | * least, it must be overridden by objects that can be converted or |
| | 782 | * that can refer to objects that can be converted). |
| | 783 | */ |
| | 784 | virtual void convert_to_const_data(VMG_ class CVmConstMapper *, |
| | 785 | vm_obj_id_t /*self*/) |
| | 786 | { /* default does nothing */ } |
| | 787 | |
| | 788 | /* |
| | 789 | * Get the type that this object uses when converted to constant |
| | 790 | * data. Objects that can be converted to constant data via |
| | 791 | * convert_to_const_data() should return the appropriate type |
| | 792 | * (VM_SSTRING, VM_LIST, etc); others can return VM_NIL to indicate |
| | 793 | * that they have no conversion. This will be called only when an |
| | 794 | * object actually has been converted as indicated in a mapper |
| | 795 | * (CVmConstMapper) object. We'll return NIL by default. |
| | 796 | */ |
| | 797 | virtual vm_datatype_t get_convert_to_const_data_type() const |
| | 798 | { return VM_NIL; } |
| | 799 | |
| | 800 | |
| | 801 | /* |
| | 802 | * Allocate memory for the fixed part from a memory manager. We |
| | 803 | * override operator new so that we can provide custom memory |
| | 804 | * management for object headers. |
| | 805 | * |
| | 806 | * To create an object, use a sequence like this: |
| | 807 | * |
| | 808 | * obj_id = mem_mgr->get_obj_table()->alloc_obj(vmg_ in_root_set); |
| | 809 | *. new (vmg_ obj_id) CVmObjString(subclass constructor params) |
| | 810 | * |
| | 811 | * The caller need not store the result of 'new'; the caller |
| | 812 | * identifies the object by the obj_id value. |
| | 813 | * |
| | 814 | * Each CVmObject subclass should provide static methods that cover |
| | 815 | * the above sequence, since it's a bit verbose to sprinkle |
| | 816 | * throughout client code. |
| | 817 | */ |
| | 818 | void *operator new(size_t siz, VMG_ vm_obj_id_t obj_id); |
| | 819 | |
| | 820 | protected: |
| | 821 | /* |
| | 822 | * Find a modifier property. This is an internal service routine that |
| | 823 | * we use to traverse the hierarchy of modifier objects associated with |
| | 824 | * our intrinsic class hierarchy. |
| | 825 | */ |
| | 826 | int find_modifier_prop(VMG_ vm_prop_id_t prop, vm_val_t *retval, |
| | 827 | vm_obj_id_t self, vm_obj_id_t orig_target_obj, |
| | 828 | vm_obj_id_t defining_obj, vm_obj_id_t *source_obj, |
| | 829 | uint *argc); |
| | 830 | |
| | 831 | /* |
| | 832 | * Service routine - check a get_prop() argument count for executing |
| | 833 | * a native code method implementation. If 'argc' is null, we'll |
| | 834 | * set the value to a VM_NATIVE_CODE indication and return true, |
| | 835 | * which the caller should do from get_prop() as well. If 'argc' is |
| | 836 | * non-null, we'll check it against the given required argument |
| | 837 | * count, and throw an error if it doesn't match. If 'argc' is |
| | 838 | * non-null and it matches the given argument count, we'll set *argc |
| | 839 | * to zero to indicate that we've consumed the arguments, and return |
| | 840 | * false - get_prop() should in this case execute the native code |
| | 841 | * and return the appropriate result value. |
| | 842 | */ |
| | 843 | static int get_prop_check_argc(vm_val_t *val, uint *argc, |
| | 844 | const CVmNativeCodeDesc *desc) |
| | 845 | { |
| | 846 | /* |
| | 847 | * if there's no 'argc', we can't execute the native code - we're |
| | 848 | * simply being asked for a description of the method, not to |
| | 849 | * evaluate its result |
| | 850 | */ |
| | 851 | if (argc == 0) |
| | 852 | { |
| | 853 | /* indicate a native code evaluation is required */ |
| | 854 | val->set_native(desc); |
| | 855 | |
| | 856 | /* tell get_prop() to return without further work */ |
| | 857 | return TRUE; |
| | 858 | } |
| | 859 | |
| | 860 | /* check the argument count - throw an error if out of range */ |
| | 861 | if (!desc->args_ok(*argc)) |
| | 862 | err_throw(VMERR_WRONG_NUM_OF_ARGS); |
| | 863 | |
| | 864 | /* everything's fine - consume the arguments */ |
| | 865 | *argc = 0; |
| | 866 | |
| | 867 | /* tell get_prop() to proceed with the native evaluation */ |
| | 868 | return FALSE; |
| | 869 | } |
| | 870 | |
| | 871 | #if 0 |
| | 872 | /* |
| | 873 | * Important note: |
| | 874 | * |
| | 875 | * This function has been removed because we no longer consider it |
| | 876 | * likely that we will ever wish to implement a relocating heap |
| | 877 | * manager. Given the large memory sizes of modern computers, and |
| | 878 | * recent academic research calling into question the conventional |
| | 879 | * wisdom that heap fragmentation is actually a problem in practical |
| | 880 | * applications, we no longer feel that maintaining the possibility of |
| | 881 | * a relocating heap manager is justified. |
| | 882 | * |
| | 883 | * Note that some code in the present implementation assumes that the |
| | 884 | * heap is non-relocating, so if a relocating heap manager is ever |
| | 885 | * implemented, those assumptions will have to be addressed. For |
| | 886 | * example, the CVmObjTads code stores direct object pointers inside |
| | 887 | * its objects, which is only possible with a non-relocating heap. |
| | 888 | */ |
| | 889 | |
| | 890 | /* |
| | 891 | * Adjust the extension pointer for a change - this must be called by |
| | 892 | * the variable heap page when compacting the heap or when the object |
| | 893 | * must be reallocated. |
| | 894 | * |
| | 895 | * This routine can be called ONLY during garbage collection. The |
| | 896 | * heap manager is not allowed to move variable-size blocks at any |
| | 897 | * other time. |
| | 898 | * |
| | 899 | * This method is virtual because a given object could have more than |
| | 900 | * one block allocated in the variable heap. By default, we'll fix up |
| | 901 | * the address of our extension if the block being moved (as |
| | 902 | * identified by its old address) matches our existing extension |
| | 903 | * pointer. |
| | 904 | */ |
| | 905 | virtual void move_var_part(void *old_pos, void *new_pos) |
| | 906 | { |
| | 907 | /* if the block being moved is our extension, note the new location */ |
| | 908 | if (ext_ == (char *)old_pos) |
| | 909 | ext_ = (char *)new_pos; |
| | 910 | } |
| | 911 | #endif |
| | 912 | |
| | 913 | /* |
| | 914 | * Pointer to extension. This pointer may be used in any way |
| | 915 | * desired by the subclassed implementation of the CVmObject |
| | 916 | * interface; normally, this pointer will contain the address of the |
| | 917 | * data associated with the object. |
| | 918 | */ |
| | 919 | char *ext_; |
| | 920 | |
| | 921 | /* property evaluator - undefined function */ |
| | 922 | int getp_undef(VMG_ vm_obj_id_t self, vm_val_t *retval, uint *argc, |
| | 923 | vm_prop_id_t prop, vm_obj_id_t *source_obj); |
| | 924 | |
| | 925 | /* property evaluator - ofKind */ |
| | 926 | int getp_of_kind(VMG_ vm_obj_id_t self, vm_val_t *retval, uint *argc, |
| | 927 | vm_prop_id_t prop, vm_obj_id_t *source_obj); |
| | 928 | |
| | 929 | /* property evaluator - getSuperclassList */ |
| | 930 | int getp_sclist(VMG_ vm_obj_id_t self, vm_val_t *retval, uint *argc, |
| | 931 | vm_prop_id_t prop, vm_obj_id_t *source_obj); |
| | 932 | |
| | 933 | /* property evaluator - propDefined */ |
| | 934 | int getp_propdef(VMG_ vm_obj_id_t self, vm_val_t *retval, uint *argc, |
| | 935 | vm_prop_id_t prop, vm_obj_id_t *source_obj); |
| | 936 | |
| | 937 | /* property evaluator - propType */ |
| | 938 | int getp_proptype(VMG_ vm_obj_id_t self, vm_val_t *retval, uint *argc, |
| | 939 | vm_prop_id_t prop, vm_obj_id_t *source_obj); |
| | 940 | |
| | 941 | /* property evaluator - getPropList */ |
| | 942 | int getp_get_prop_list(VMG_ vm_obj_id_t self, vm_val_t *retval, |
| | 943 | uint *argc, vm_prop_id_t prop, |
| | 944 | vm_obj_id_t *source_obj); |
| | 945 | |
| | 946 | /* property evaluator - getPropParams */ |
| | 947 | int getp_get_prop_params(VMG_ vm_obj_id_t self, vm_val_t *retval, |
| | 948 | uint *argc, vm_prop_id_t prop, |
| | 949 | vm_obj_id_t *source_obj); |
| | 950 | |
| | 951 | /* property evaluator - isClass */ |
| | 952 | int getp_is_class(VMG_ vm_obj_id_t self, vm_val_t *retval, |
| | 953 | uint *argc, vm_prop_id_t prop, |
| | 954 | vm_obj_id_t *source_obj); |
| | 955 | |
| | 956 | /* property evaluator - propInherited */ |
| | 957 | int getp_propinh(VMG_ vm_obj_id_t self, vm_val_t *retval, |
| | 958 | uint *argc, vm_prop_id_t prop, |
| | 959 | vm_obj_id_t *source_obj); |
| | 960 | |
| | 961 | /* property evaluator - isTransient */ |
| | 962 | int getp_is_transient(VMG_ vm_obj_id_t self, vm_val_t *retval, |
| | 963 | uint *argc, vm_prop_id_t prop, |
| | 964 | vm_obj_id_t *source_obj); |
| | 965 | |
| | 966 | /* find the intrinsic class for the given modifier object */ |
| | 967 | virtual vm_obj_id_t find_intcls_for_mod(VMG_ vm_obj_id_t self, |
| | 968 | vm_obj_id_t mod_obj); |
| | 969 | |
| | 970 | /* property evaluation function table */ |
| | 971 | static int (CVmObject::*func_table_[])(VMG_ vm_obj_id_t self, |
| | 972 | vm_val_t *retval, uint *argc, |
| | 973 | vm_prop_id_t prop, |
| | 974 | vm_obj_id_t *source_obj); |
| | 975 | }; |
| | 976 | |
| | 977 | /* |
| | 978 | * Function table indices for 'Object' intrinsic class methods. Each of |
| | 979 | * these gives the index in our function table for the property ID (as |
| | 980 | * defined in the image file) corresponding to that method. |
| | 981 | */ |
| | 982 | const int VMOBJ_IDX_OF_KIND = 1; |
| | 983 | const int VMOBJ_IDX_SCLIST = 2; |
| | 984 | const int VMOBJ_IDX_PROPDEF = 3; |
| | 985 | const int VMOBJ_IDX_PROPTYPE = 4; |
| | 986 | const int VMOBJ_IDX_GET_PROP_LIST = 5; |
| | 987 | const int VMOBJ_IDX_GET_PROP_PARAMS = 6; |
| | 988 | const int VMOBJ_IDX_IS_CLASS = 7; |
| | 989 | const int VMOBJ_IDX_PROPINH = 8; |
| | 990 | const int VMOBJ_IDX_IS_TRANSIENT = 9; |
| | 991 | |
| | 992 | /* ------------------------------------------------------------------------ */ |
| | 993 | /* |
| | 994 | * Each CVmObject subclass must define a singleton instance of |
| | 995 | * CVmMetaclass that describes the class and instantiates objects of the |
| | 996 | * class. |
| | 997 | */ |
| | 998 | |
| | 999 | /* |
| | 1000 | * The "registration index" is set at startup, when we register the |
| | 1001 | * metaclasses. This is static to the class - it isn't a per-instance |
| | 1002 | * value. The purpose of setting this value is to allow |
| | 1003 | * get_registration_index() in the instance to get the class value. |
| | 1004 | * This information is used when saving our state to save information on |
| | 1005 | * an instance's metaclass, so that the instance can be re-created when |
| | 1006 | * the saved state is restored. |
| | 1007 | * |
| | 1008 | * The registration index is NOT persistent data. The registration |
| | 1009 | * index of a particular metaclass can vary from execution to execution |
| | 1010 | * (although it will remain fixed throughout the lifetime of one set of |
| | 1011 | * VM globals, hence throughout an image file's execution). The |
| | 1012 | * registration index allows us to find the registration table entry, |
| | 1013 | * which in turn will give us the metaclass global ID, which is suitable |
| | 1014 | * for persistent storage. |
| | 1015 | */ |
| | 1016 | |
| | 1017 | class CVmMetaclass |
| | 1018 | { |
| | 1019 | public: |
| | 1020 | /* |
| | 1021 | * Get the metaclass registration table index. This gives the index |
| | 1022 | * of the metaclass in the system registration table. This value is |
| | 1023 | * fixed during execution; it is set during startup, when the |
| | 1024 | * registration table is being built, and never changes after that. |
| | 1025 | */ |
| | 1026 | uint get_reg_idx() const { return meta_reg_idx_; } |
| | 1027 | |
| | 1028 | /* |
| | 1029 | * Get the metaclass name. This is the universally unique |
| | 1030 | * identifier assigned to the metaclass, and is the name used to |
| | 1031 | * dynamically link the image file to the metaclass. |
| | 1032 | */ |
| | 1033 | virtual const char *get_meta_name() const = 0; |
| | 1034 | |
| | 1035 | /* |
| | 1036 | * Create an instance of the metaclass using arguments from the VM |
| | 1037 | * stack. Returns the ID of the newly-created object. If the class |
| | 1038 | * has a byte-code constructor, invoke the constructor using the |
| | 1039 | * normal call-procedure protocol. Leave the result in register R0. |
| | 1040 | */ |
| | 1041 | virtual vm_obj_id_t create_from_stack(VMG_ const uchar **pc_ptr, |
| | 1042 | uint argc) = 0; |
| | 1043 | |
| | 1044 | /* |
| | 1045 | * Create an instance of the metaclass with the given ID. The |
| | 1046 | * object table entry will already be allocated by the caller; this |
| | 1047 | * ro utine only needs to invoke the metaclass-specific constructor |
| | 1048 | * to initialize the object. The object must be initialized in such |
| | 1049 | * a way that it can subsequently be loaded from the image file with |
| | 1050 | * load_from_iamge(). In general, this routine only needs to do |
| | 1051 | * something like this: |
| | 1052 | * |
| | 1053 | * new (vmg_ id) CVmObjXxx(); |
| | 1054 | */ |
| | 1055 | virtual void create_for_image_load(VMG_ vm_obj_id_t id) = 0; |
| | 1056 | |
| | 1057 | /* |
| | 1058 | * Create an instance of the metaclass with the given ID in |
| | 1059 | * preparation for restoring the object from a saved state file. |
| | 1060 | */ |
| | 1061 | virtual void create_for_restore(VMG_ vm_obj_id_t id) = 0; |
| | 1062 | |
| | 1063 | /* |
| | 1064 | * Call a static property of the metaclass |
| | 1065 | */ |
| | 1066 | virtual int call_stat_prop(VMG_ vm_val_t *result, |
| | 1067 | const uchar **pc_ptr, uint *argc, |
| | 1068 | vm_prop_id_t prop) = 0; |
| | 1069 | |
| | 1070 | /* |
| | 1071 | * Get the number of superclasses of the metaclass, and get the |
| | 1072 | * super-metaclass at the given index. All metaclasses have exactly |
| | 1073 | * one super-metaclass, except for the root object metaclass, which |
| | 1074 | * has no super-metaclass. |
| | 1075 | */ |
| | 1076 | virtual int get_supermeta_count(VMG0_) const { return 1; } |
| | 1077 | virtual vm_obj_id_t get_supermeta(VMG_ int idx) const; |
| | 1078 | |
| | 1079 | /* determine if I'm an instance of the given object */ |
| | 1080 | virtual int is_meta_instance_of(VMG_ vm_obj_id_t obj) const; |
| | 1081 | |
| | 1082 | /* |
| | 1083 | * set the metaclass registration table index - this can only be |
| | 1084 | * done by vm_register_metaclass() during initialization |
| | 1085 | */ |
| | 1086 | void set_metaclass_reg_index(uint idx) { meta_reg_idx_ = idx; } |
| | 1087 | |
| | 1088 | /* most metaclasses are simply derived from Object */ |
| | 1089 | virtual CVmMetaclass *get_supermeta_reg() const |
| | 1090 | { return CVmObject::metaclass_reg_; } |
| | 1091 | |
| | 1092 | /* |
| | 1093 | * get my class object - we'll look up our class object in the |
| | 1094 | * metaclass registration table |
| | 1095 | */ |
| | 1096 | vm_obj_id_t get_class_obj(VMG0_) const; |
| | 1097 | |
| | 1098 | private: |
| | 1099 | /* system metaclass registration table index */ |
| | 1100 | uint meta_reg_idx_; |
| | 1101 | }; |
| | 1102 | |
| | 1103 | |
| | 1104 | /* ------------------------------------------------------------------------ */ |
| | 1105 | /* |
| | 1106 | * Root Object definition. The root object can never be instantiated; |
| | 1107 | * it is defined purely to provide an object to represent as the root in |
| | 1108 | * the type system. |
| | 1109 | */ |
| | 1110 | class CVmMetaclassRoot: public CVmMetaclass |
| | 1111 | { |
| | 1112 | public: |
| | 1113 | /* get the metaclass name */ |
| | 1114 | const char *get_meta_name() const { return "root-object/030004"; } |
| | 1115 | |
| | 1116 | /* create an instance - this class cannot be instantiated */ |
| | 1117 | vm_obj_id_t create_from_stack(VMG_ const uchar **pc_ptr, uint argc) |
| | 1118 | { |
| | 1119 | err_throw(VMERR_BAD_DYNAMIC_NEW); |
| | 1120 | AFTER_ERR_THROW(return VM_INVALID_OBJ;) |
| | 1121 | } |
| | 1122 | |
| | 1123 | /* create an object - this class cannot be instantiated */ |
| | 1124 | void create_for_image_load(VMG_ vm_obj_id_t id) |
| | 1125 | { err_throw(VMERR_BAD_STATIC_NEW); } |
| | 1126 | |
| | 1127 | /* create an instance for loading from a saved state */ |
| | 1128 | void create_for_restore(VMG_ vm_obj_id_t id) |
| | 1129 | { err_throw(VMERR_BAD_STATIC_NEW); } |
| | 1130 | |
| | 1131 | /* call a static property */ |
| | 1132 | int call_stat_prop(VMG_ vm_val_t *result, |
| | 1133 | const uchar **pc_ptr, uint *argc, |
| | 1134 | vm_prop_id_t prop) |
| | 1135 | { |
| | 1136 | /* call the base object implementation */ |
| | 1137 | return CVmObject::call_stat_prop(vmg_ result, pc_ptr, argc, prop); |
| | 1138 | } |
| | 1139 | |
| | 1140 | /* the root object has no supermetaclasses */ |
| | 1141 | int get_supermeta_count(VMG0_) const { return 0; } |
| | 1142 | vm_obj_id_t get_supermeta(VMG_ int) const { return VM_INVALID_OBJ; } |
| | 1143 | |
| | 1144 | /* |
| | 1145 | * determine if I'm an instance of the given object - the root |
| | 1146 | * object is not a subclass of anything |
| | 1147 | */ |
| | 1148 | virtual int is_meta_instance_of(VMG_ vm_obj_id_t obj) const |
| | 1149 | { return FALSE; } |
| | 1150 | |
| | 1151 | /* the base Object metaclass has no supermetaclass */ |
| | 1152 | virtual CVmMetaclass *get_supermeta_reg() const { return 0; } |
| | 1153 | }; |
| | 1154 | |
| | 1155 | /* ------------------------------------------------------------------------ */ |
| | 1156 | /* |
| | 1157 | * Object fixup table entry |
| | 1158 | */ |
| | 1159 | struct obj_fixup_entry |
| | 1160 | { |
| | 1161 | /* old ID */ |
| | 1162 | vm_obj_id_t old_id; |
| | 1163 | |
| | 1164 | /* new ID */ |
| | 1165 | vm_obj_id_t new_id; |
| | 1166 | }; |
| | 1167 | |
| | 1168 | /* |
| | 1169 | * Object ID Fixup Table. This is used during state restoration to map |
| | 1170 | * from the saved state file's object numbering system to the new |
| | 1171 | * in-memory numbering system. |
| | 1172 | * |
| | 1173 | * The objects must be added to the table IN ASCENDING ORDER OF OLD ID. |
| | 1174 | * We assume this sorting order to perform a binary lookup when asked to |
| | 1175 | * map an ID. |
| | 1176 | */ |
| | 1177 | |
| | 1178 | /* fixup table subarray size */ |
| | 1179 | #define VMOBJFIXUP_SUB_SIZE 2048 |
| | 1180 | |
| | 1181 | class CVmObjFixup |
| | 1182 | { |
| | 1183 | public: |
| | 1184 | CVmObjFixup(ulong entry_cnt); |
| | 1185 | ~CVmObjFixup(); |
| | 1186 | |
| | 1187 | /* add a fixup to the table */ |
| | 1188 | void add_fixup(vm_obj_id_t old_id, vm_obj_id_t new_id); |
| | 1189 | |
| | 1190 | /* |
| | 1191 | * Translate from the file numbering system to the new numbering |
| | 1192 | * system. If the object isn't found, it must be a static object and |
| | 1193 | * hence doesn't require translation, so we'll return the original ID |
| | 1194 | * unchanged. |
| | 1195 | */ |
| | 1196 | vm_obj_id_t get_new_id(VMG_ vm_obj_id_t old_id); |
| | 1197 | |
| | 1198 | /* fix up a DATAHOLDER value */ |
| | 1199 | void fix_dh(VMG_ char *dh); |
| | 1200 | |
| | 1201 | /* fix up an array of DATAHOLDER values */ |
| | 1202 | void fix_dh_array(VMG_ char *arr, size_t cnt); |
| | 1203 | |
| | 1204 | /* fix a portable VMB_OBJECT_ID field */ |
| | 1205 | void fix_vmb_obj(VMG_ char *p); |
| | 1206 | |
| | 1207 | /* fix an array of portable VMB_OBJECT_ID fields */ |
| | 1208 | void fix_vmb_obj_array(VMG_ char *p, size_t cnt); |
| | 1209 | |
| | 1210 | private: |
| | 1211 | /* find an entry given the old object ID */ |
| | 1212 | struct obj_fixup_entry *find_entry(vm_obj_id_t old_entry); |
| | 1213 | |
| | 1214 | /* get an entry at the given array index */ |
| | 1215 | struct obj_fixup_entry *get_entry(ulong idx) const |
| | 1216 | { |
| | 1217 | return &arr_[idx / VMOBJFIXUP_SUB_SIZE][idx % VMOBJFIXUP_SUB_SIZE]; |
| | 1218 | } |
| | 1219 | |
| | 1220 | /* array of subarrays */ |
| | 1221 | struct obj_fixup_entry **arr_; |
| | 1222 | |
| | 1223 | /* number of subarray pages */ |
| | 1224 | ulong pages_; |
| | 1225 | |
| | 1226 | /* number of entries in the array */ |
| | 1227 | ulong cnt_; |
| | 1228 | |
| | 1229 | /* number of entries used so far */ |
| | 1230 | ulong used_; |
| | 1231 | }; |
| | 1232 | |
| | 1233 | |
| | 1234 | /* ------------------------------------------------------------------------ */ |
| | 1235 | /* |
| | 1236 | * Global variable structure. We maintain a linked list of these |
| | 1237 | * structures for miscellaneous global variables required by other |
| | 1238 | * subsystems. |
| | 1239 | */ |
| | 1240 | struct vm_globalvar_t |
| | 1241 | { |
| | 1242 | /* the variable's value */ |
| | 1243 | vm_val_t val; |
| | 1244 | |
| | 1245 | /* next and previous pointer in linked list of global variables */ |
| | 1246 | vm_globalvar_t *nxt; |
| | 1247 | vm_globalvar_t *prv; |
| | 1248 | }; |
| | 1249 | |
| | 1250 | |
| | 1251 | /* ------------------------------------------------------------------------ */ |
| | 1252 | /* |
| | 1253 | * Global object page. We keep a linked list of pages of globals that |
| | 1254 | * refer to objects that are always reachable but were not loaded from the |
| | 1255 | * image file. |
| | 1256 | */ |
| | 1257 | class CVmObjGlobPage |
| | 1258 | { |
| | 1259 | public: |
| | 1260 | CVmObjGlobPage() |
| | 1261 | { |
| | 1262 | /* we're not in a list yet */ |
| | 1263 | nxt_ = 0; |
| | 1264 | |
| | 1265 | /* we have no allocated entries yet */ |
| | 1266 | used_ = 0; |
| | 1267 | } |
| | 1268 | |
| | 1269 | ~CVmObjGlobPage() |
| | 1270 | { |
| | 1271 | /* delete the next page */ |
| | 1272 | delete nxt_; |
| | 1273 | } |
| | 1274 | |
| | 1275 | /* |
| | 1276 | * add an entry to this page; returns true on success, false if we're |
| | 1277 | * too full to add another entry |
| | 1278 | */ |
| | 1279 | int add_entry(vm_obj_id_t obj) |
| | 1280 | { |
| | 1281 | /* if we're full, indicate failure */ |
| | 1282 | if (used_ == sizeof(objs_)/sizeof(objs_[0])) |
| | 1283 | return FALSE; |
| | 1284 | |
| | 1285 | /* store the entry and count it */ |
| | 1286 | objs_[used_] = obj; |
| | 1287 | ++used_; |
| | 1288 | |
| | 1289 | /* indicate success */ |
| | 1290 | return TRUE; |
| | 1291 | } |
| | 1292 | |
| | 1293 | /* next page in list */ |
| | 1294 | CVmObjGlobPage *nxt_; |
| | 1295 | |
| | 1296 | /* number of entries on this page that are in use */ |
| | 1297 | size_t used_; |
| | 1298 | |
| | 1299 | /* array of entries on this page */ |
| | 1300 | vm_obj_id_t objs_[30]; |
| | 1301 | }; |
| | 1302 | |
| | 1303 | /* ------------------------------------------------------------------------ */ |
| | 1304 | /* |
| | 1305 | * Object Header Manager |
| | 1306 | */ |
| | 1307 | |
| | 1308 | /* |
| | 1309 | * Number of objects in a page. We constrain this to be a power of two |
| | 1310 | * to make certain calculations fast (in particular, so that division by |
| | 1311 | * and modulo the page count can be done as bit shifts and masks). |
| | 1312 | */ |
| | 1313 | const unsigned int VM_OBJ_PAGE_CNT_LOG2 = 12; |
| | 1314 | const unsigned int VM_OBJ_PAGE_CNT = (1 << VM_OBJ_PAGE_CNT_LOG2); |
| | 1315 | |
| | 1316 | /* |
| | 1317 | * Reachability states. A Reachable object is in the root set, or can |
| | 1318 | * be reached directly or indirectly from the root set. A |
| | 1319 | * Finalizer-Reachable object can be reached directly or indirectly from |
| | 1320 | * a finalizable object that is Finalizer-Reachable or Unreachable, but |
| | 1321 | * not from any reachable object. An Unreachable object cannot be |
| | 1322 | * reached from any Reachable or Finalizable object. |
| | 1323 | * |
| | 1324 | * We deliberately arrange the objects in a hierarchical order: |
| | 1325 | * Finalizer-Reachable is "more reachable" than Unreachable, and |
| | 1326 | * Reachable is more reachable than Finalizer-Reachable. The numeric |
| | 1327 | * values of these states are arranged so that a higher number indicates |
| | 1328 | * stronger reachability. |
| | 1329 | */ |
| | 1330 | #define VMOBJ_UNREACHABLE 0x00 |
| | 1331 | #define VMOBJ_F_REACHABLE 0x01 |
| | 1332 | #define VMOBJ_REACHABLE 0x02 |
| | 1333 | |
| | 1334 | /* |
| | 1335 | * Finalization states. An Unfinalizable object is one which has not |
| | 1336 | * ever been detected by the garbage collector to be less than fully |
| | 1337 | * Reachable. A Finalizable object is one which has been found during a |
| | 1338 | * garbage collection pass to be either F-Reachable or Unreachable, but |
| | 1339 | * which has not yet been finalized. A Finalized object is one which |
| | 1340 | * has had its finalizer method invoked. |
| | 1341 | */ |
| | 1342 | #define VMOBJ_UNFINALIZABLE 0x00 |
| | 1343 | #define VMOBJ_FINALIZABLE 0x01 |
| | 1344 | #define VMOBJ_FINALIZED 0x02 |
| | 1345 | |
| | 1346 | |
| | 1347 | /* |
| | 1348 | * Object table page entry. |
| | 1349 | */ |
| | 1350 | struct CVmObjPageEntry |
| | 1351 | { |
| | 1352 | /* |
| | 1353 | * An entry is either a member of the free list, or it's a valid |
| | 1354 | * object. |
| | 1355 | */ |
| | 1356 | union |
| | 1357 | { |
| | 1358 | /* |
| | 1359 | * If it's not in the free list, then it's a VM object. We |
| | 1360 | * can't actually embed a true CVmObject here - that's an |
| | 1361 | * abstract type and we therefore can't directly instantiate it |
| | 1362 | * through embedding. So, just allocate enough space for the |
| | 1363 | * object; the memory manager will claim this space (via |
| | 1364 | * operator new) and store the actual CVmObject here when the |
| | 1365 | * slot is allocated to an object. |
| | 1366 | */ |
| | 1367 | char obj_[sizeof(CVmObject)]; |
| | 1368 | |
| | 1369 | /* |
| | 1370 | * if it's in the free list, we just have a pointer to the |
| | 1371 | * previous element of the free list |
| | 1372 | */ |
| | 1373 | vm_obj_id_t prev_free_; |
| | 1374 | } ptr_; |
| | 1375 | |
| | 1376 | /* next object in list (either the GC work queue or the free list) */ |
| | 1377 | vm_obj_id_t next_obj_; |
| | 1378 | |
| | 1379 | /* get my VM object pointer */ |
| | 1380 | CVmObject *get_vm_obj() const { return (CVmObject *)ptr_.obj_; } |
| | 1381 | |
| | 1382 | /* flag: the object is in the free list */ |
| | 1383 | int free_ : 1; |
| | 1384 | |
| | 1385 | /* |
| | 1386 | * flag: the object is part of the root set (that is, there's a |
| | 1387 | * reference to this object from some static location outside of the |
| | 1388 | * root set, such as in p-code or in a constant list) |
| | 1389 | */ |
| | 1390 | int in_root_set_ : 1; |
| | 1391 | |
| | 1392 | /* |
| | 1393 | * Reachability state. This indicates whether the object is |
| | 1394 | * reachable, reachable from finalizable objects only, or |
| | 1395 | * unreachable. This is set during garbage collection. |
| | 1396 | */ |
| | 1397 | uint reachable_ : 2; |
| | 1398 | |
| | 1399 | /* |
| | 1400 | * Finalization state. This indicates whether an object is |
| | 1401 | * unfinalizable, finalizable, or finalized. |
| | 1402 | */ |
| | 1403 | uint finalize_state_ : 2; |
| | 1404 | |
| | 1405 | /* |
| | 1406 | * Flag: the object is part of an undo savepoint. This is cleared |
| | 1407 | * when an object is initially created, and set for all existing |
| | 1408 | * objects when an undo savepoint is created - this means that this |
| | 1409 | * will be set for an object only if an undo savepoint has been |
| | 1410 | * created since the object was created. |
| | 1411 | * |
| | 1412 | * When the undo mechanism is asked to create an undo record |
| | 1413 | * associated with an object, it will do nothing if the object is not |
| | 1414 | * part of the undo savepoint. This means that we won't save undo |
| | 1415 | * records for objects created since the start of the most recent |
| | 1416 | * savepoint - keeping undo for such objects is unnecessary, since if |
| | 1417 | * we roll back to the savepoint, the object won't even be in |
| | 1418 | * existence any more and hence has no need to restore any of its |
| | 1419 | * state. |
| | 1420 | */ |
| | 1421 | uint in_undo_ : 1; |
| | 1422 | |
| | 1423 | /* |
| | 1424 | * Flag: the object is "transient." A transient object does not |
| | 1425 | * participate in undo, is not saved or restored to a saved state, and |
| | 1426 | * is not affected by restarting. |
| | 1427 | */ |
| | 1428 | uint transient_ : 1; |
| | 1429 | |
| | 1430 | /* flag: the object has requested post-load initialization */ |
| | 1431 | uint requested_post_load_init_ : 1; |
| | 1432 | |
| | 1433 | /* |
| | 1434 | * Garbage collection hint flags. These flags provide hints on how |
| | 1435 | * the object's metaclass interacts with the garbage collector. These |
| | 1436 | * do NOT indicate the object's current status, but rather indicate |
| | 1437 | * the metaclass's capabilities - so 'can_have_refs_' does not |
| | 1438 | * indicate that the object current has or doesn't have any references |
| | 1439 | * to other objects, but rather indicates if it's POSSIBLE for the |
| | 1440 | * object EVER to have references to other objects. For example, all |
| | 1441 | * Strings would set 'can_have_refs_' to false, and all TadsObjects |
| | 1442 | * would set it to true. |
| | 1443 | * |
| | 1444 | * We set these flags to true by default, for the maximally |
| | 1445 | * conservative settings. A metaclass can simply ignore these |
| | 1446 | * settings and be assured of correct GC behavior. However, if a |
| | 1447 | * metaclass knows that it can correctly set one of these flags to |
| | 1448 | * false, it should do so after instances are created, because doing |
| | 1449 | * so allows the garbage collector to reduce the amount of work it |
| | 1450 | * must do for the object. |
| | 1451 | * |
| | 1452 | * 'can_have_refs_' indicates if the object can ever contain |
| | 1453 | * references to other objects. By default, this is always set to |
| | 1454 | * true, but a metaclass that is not capable of storing references to |
| | 1455 | * other objects should set this to false. When this is set to false, |
| | 1456 | * the garbage collector will avoid tracing into this object when |
| | 1457 | * tracing references, because it will know in advance that tracing |
| | 1458 | * into the object will have no effect. |
| | 1459 | * |
| | 1460 | * 'can_have_weak_refs_' indicates if the object can ever contain weak |
| | 1461 | * references to other objects. By default, this is set to true, but |
| | 1462 | * a metaclass that never uses weak references can set it to false. |
| | 1463 | * When this is set to false, the garbage collector can avoid |
| | 1464 | * notifying this object of the need to remove stale weak references. |
| | 1465 | * |
| | 1466 | * IMPORTANT: We assume that a metaclass that cannot have |
| | 1467 | * references/weak references must ALSO never have references/weak |
| | 1468 | * references) in its undo information. It's hard to imagine a case |
| | 1469 | * where we'd have no possibility of a kind of references in an object |
| | 1470 | * but still have the possibility of the same kind of references in |
| | 1471 | * the object's undo records; but should such a case arise, the |
| | 1472 | * metaclass must indicate that it does have the possibility of that |
| | 1473 | * kind of references. |
| | 1474 | */ |
| | 1475 | uint can_have_refs_ : 1; |
| | 1476 | uint can_have_weak_refs_ : 1; |
| | 1477 | |
| | 1478 | /* |
| | 1479 | * An entry is deletable if it's unreachable and has been finalized. |
| | 1480 | * If the entry is marked as free, it's already been deleted, hence |
| | 1481 | * is certainly deletable. |
| | 1482 | */ |
| | 1483 | int is_deletable() const |
| | 1484 | { |
| | 1485 | return (free_ |
| | 1486 | || (reachable_ == VMOBJ_UNREACHABLE |
| | 1487 | && finalize_state_ == VMOBJ_FINALIZED)); |
| | 1488 | } |
| | 1489 | |
| | 1490 | /* |
| | 1491 | * Determine if the object should participate in undo. An object |
| | 1492 | * participates in undo if it existed as of the most recent savepoint, |
| | 1493 | * and the object is not transient. |
| | 1494 | */ |
| | 1495 | int is_in_undo() const { return in_undo_ && !transient_; } |
| | 1496 | |
| | 1497 | /* |
| | 1498 | * A "saveable" object is one which must be written to a saved state |
| | 1499 | * file. |
| | 1500 | * |
| | 1501 | * To be saveable, an object must not be free, must not be transient, |
| | 1502 | * must be fully reachable, and must have been modified since loading |
| | 1503 | * (or simply have been created dynamically, since all an object that |
| | 1504 | * was created dynamically has inherently been modified since the |
| | 1505 | * program was loaded, as it didn't even exist when the program was |
| | 1506 | * loaded). |
| | 1507 | * |
| | 1508 | * Do not save objects that are only reachable through a finalizer; |
| | 1509 | * assume that these objects do not figure into the persistent VM |
| | 1510 | * state, but are still around merely because they have some external |
| | 1511 | * resource deallocation to which they must yet tend. |
| | 1512 | * |
| | 1513 | * Do not save objects that are in the root set and which haven't been |
| | 1514 | * modified since loading. We always reset before restoring to the |
| | 1515 | * initial image file state, so there's no need to save data for any |
| | 1516 | * object that's simply in its initial image file state. |
| | 1517 | */ |
| | 1518 | int is_saveable() const |
| | 1519 | { |
| | 1520 | return (!free_ |
| | 1521 | && !transient_ |
| | 1522 | && reachable_ == VMOBJ_REACHABLE |
| | 1523 | && (!in_root_set_ |
| | 1524 | || get_vm_obj()->is_changed_since_load())); |
| | 1525 | } |
| | 1526 | |
| | 1527 | /* |
| | 1528 | * Determine if the object is "persistent." A persistent object is |
| | 1529 | * one which will survive saving and restoring machine state. An |
| | 1530 | * object is persistent if it is not transient, and either it is |
| | 1531 | * saveable (in which case it will be explicitly saved and restored), |
| | 1532 | * or it is merely in the root set (in which case it is always |
| | 1533 | * present). |
| | 1534 | */ |
| | 1535 | int is_persistent() const |
| | 1536 | { |
| | 1537 | return !transient_ && (in_root_set_ || is_saveable()); |
| | 1538 | } |
| | 1539 | }; |
| | 1540 | |
| | 1541 | /* ------------------------------------------------------------------------ */ |
| | 1542 | /* |
| | 1543 | * Object table. |
| | 1544 | */ |
| | 1545 | class CVmObjTable |
| | 1546 | { |
| | 1547 | public: |
| | 1548 | /* create the table */ |
| | 1549 | CVmObjTable() { init(); } |
| | 1550 | |
| | 1551 | /* initialize */ |
| | 1552 | void init(); |
| | 1553 | |
| | 1554 | /* |
| | 1555 | * Destroy the table - call this rather BEFORE using operator delete |
| | 1556 | * directly. After this routine is called, the object table can be |
| | 1557 | * deleted. |
| | 1558 | */ |
| | 1559 | void delete_obj_table(VMG0_); |
| | 1560 | |
| | 1561 | /* clients must call delete_obj_table() before deleting the object */ |
| | 1562 | ~CVmObjTable(); |
| | 1563 | |
| | 1564 | /* get an object given an object ID */ |
| | 1565 | inline CVmObject *get_obj(vm_obj_id_t id) const |
| | 1566 | { |
| | 1567 | /* get the page entry, and get the object from the entry */ |
| | 1568 | return (CVmObject *)&get_entry(id)->ptr_.obj_; |
| | 1569 | } |
| | 1570 | |
| | 1571 | /* |
| | 1572 | * Turn garbage collection on or off. When performing a series of |
| | 1573 | * allocations of values that won't be stored on the stack, this can |
| | 1574 | * be used to ensure that the intermediate allocations aren't |
| | 1575 | * collected as unreferenced before the group of operations is |
| | 1576 | * completed. Returns previous status for later restoration. |
| | 1577 | */ |
| | 1578 | int enable_gc(VMG_ int enable); |
| | 1579 | |
| | 1580 | /* allocate a new object ID */ |
| | 1581 | vm_obj_id_t alloc_obj(VMG_ int in_root_set) |
| | 1582 | { |
| | 1583 | /* allocate, using maximally conservative GC characteristics */ |
| | 1584 | return alloc_obj(vmg_ in_root_set, TRUE, TRUE); |
| | 1585 | } |
| | 1586 | |
| | 1587 | /* allocate a new object ID */ |
| | 1588 | vm_obj_id_t alloc_obj(VMG_ int in_root_set, int can_have_refs, |
| | 1589 | int can_have_weak_refs); |
| | 1590 | |
| | 1591 | /* |
| | 1592 | * Allocate an object at a particular object ID. This is used when |
| | 1593 | * loading objects from an image file or restoring objects from a |
| | 1594 | * saved state file, since objects must be loaded or restored with |
| | 1595 | * the same object number which they were originally assigned. This |
| | 1596 | * routine throws an error if the object is already allocated. |
| | 1597 | */ |
| | 1598 | void alloc_obj_with_id(vm_obj_id_t id, int in_root_set) |
| | 1599 | { |
| | 1600 | /* allocate with maximally conservative GC characteristics */ |
| | 1601 | alloc_obj_with_id(id, in_root_set, TRUE, TRUE); |
| | 1602 | } |
| | 1603 | |
| | 1604 | /* allocate an object with a given ID */ |
| | 1605 | void alloc_obj_with_id(vm_obj_id_t id, int in_root_set, |
| | 1606 | int can_have_refs, int can_have_weak_refs); |
| | 1607 | |
| | 1608 | /* |
| | 1609 | * Collect all garbage. This runs an entire garbage collection pass |
| | 1610 | * to completion with a single call. This can be used for |
| | 1611 | * simplicity when the caller does not require incremental operation |
| | 1612 | * of the garbage collector. |
| | 1613 | * |
| | 1614 | * This function actually performs two garbage collection passes to |
| | 1615 | * ensure that all collectible objects are collected. We perform |
| | 1616 | * one pass to detect finalizable objects, then finalize all objects |
| | 1617 | * that we can, then make one more pass to sweep up all of the |
| | 1618 | * finalized objects that can be deleted. |
| | 1619 | */ |
| | 1620 | void gc_full(VMG0_); |
| | 1621 | |
| | 1622 | /* |
| | 1623 | * Incremental garbage collection. Call gc_pass_init() to |
| | 1624 | * initialize the pass. Call gc_pass_continue() repeatedly to |
| | 1625 | * perform incremental collection; this routine runs for a short |
| | 1626 | * time and then returns. gc_pass_continue() returns true if |
| | 1627 | * there's more work to do, false if not, so the caller can stop |
| | 1628 | * invoking it as soon as it returns false. Call gc_pass_finish() |
| | 1629 | * to complete the garbage collection. gc_pass_finish() will call |
| | 1630 | * gc_pass_continue() if necessary to finish its work, so the caller |
| | 1631 | * need not keep invoking gc_pass_continue() if it runs out of work |
| | 1632 | * to interleave with the garbage collector. |
| | 1633 | * |
| | 1634 | * Once garbage collection is started, it must be finished before |
| | 1635 | * any other VM activity occurs. So, after a call to |
| | 1636 | * gc_pass_init(), the caller is not allowed to perform any other VM |
| | 1637 | * operations until gc_pass_finish() returns (in particular, no new |
| | 1638 | * objects may be created, and no references to existing objects may |
| | 1639 | * be created or changed). |
| | 1640 | */ |
| | 1641 | void gc_pass_init(VMG0_); |
| | 1642 | int gc_pass_continue(VMG0_) { return gc_pass_continue(vmg_ TRUE); } |
| | 1643 | void gc_pass_finish(VMG0_); |
| | 1644 | |
| | 1645 | /* |
| | 1646 | * Run pending finalizers. This can be run at any time other than |
| | 1647 | * during garbage collection (i.e., between gc_pass_init() and |
| | 1648 | * gc_pass_finish()). |
| | 1649 | */ |
| | 1650 | void run_finalizers(VMG0_); |
| | 1651 | |
| | 1652 | /* |
| | 1653 | * Determine if a given object is subject to deletion. This only |
| | 1654 | * gives meaningful results during the final garbage collector pass. |
| | 1655 | * Returns true if the object is ready for deletion, which can only |
| | 1656 | * happen when the object is both Unreachable and Finalized, or |
| | 1657 | * false if it not. A true return means that the object can be |
| | 1658 | * deleted at any time. This can be used by weak referencers to |
| | 1659 | * determine if objects they are referencing are about to be |
| | 1660 | * deleted, and thus that the weak reference must be forgotten. |
| | 1661 | */ |
| | 1662 | int is_obj_deletable(vm_obj_id_t obj) const |
| | 1663 | { |
| | 1664 | /* |
| | 1665 | * If it's not a valid object, consider it deletable, since it |
| | 1666 | * has indeed already been deleted; otherwise, it's deletable if |
| | 1667 | * its object table entry is deletable. |
| | 1668 | */ |
| | 1669 | return (obj == VM_INVALID_OBJ |
| | 1670 | || get_entry(obj)->is_deletable()); |
| | 1671 | } |
| | 1672 | |
| | 1673 | /* |
| | 1674 | * Mark object references (for GC tracing) made in an object's undo |
| | 1675 | * record. If the object is marked as having no possibility of |
| | 1676 | * containing references to other objects, we won't bother invoking |
| | 1677 | * the object's tracing method, as we can be assured that the undo |
| | 1678 | * records won't contain any references either. |
| | 1679 | */ |
| | 1680 | void mark_obj_undo_rec(VMG_ vm_obj_id_t obj, |
| | 1681 | struct CVmUndoRecord *undo_rec) |
| | 1682 | { |
| | 1683 | CVmObjPageEntry *entry; |
| | 1684 | |
| | 1685 | /* get the object entry */ |
| | 1686 | entry = get_entry(obj); |
| | 1687 | |
| | 1688 | /* |
| | 1689 | * if the object can have any references, mark any references the |
| | 1690 | * object makes from the undo record; if the object can't have any |
| | 1691 | * references, assume its undo records cannot either, in which |
| | 1692 | * case there should be nothing to mark |
| | 1693 | */ |
| | 1694 | if (entry->can_have_refs_) |
| | 1695 | entry->get_vm_obj()->mark_undo_ref(vmg_ undo_rec); |
| | 1696 | } |
| | 1697 | |
| | 1698 | /* |
| | 1699 | * Remove stale weak undo references for an object. If the object is |
| | 1700 | * marked as having no possibility of weak references, we won't bother |
| | 1701 | * invoking the object's weak undo reference remover method, since we |
| | 1702 | * know it won't do anything. |
| | 1703 | */ |
| | 1704 | void remove_obj_stale_undo_weak_ref(VMG_ vm_obj_id_t obj, |
| | 1705 | struct CVmUndoRecord *undo_rec) |
| | 1706 | { |
| | 1707 | CVmObjPageEntry *entry; |
| | 1708 | |
| | 1709 | /* get the object entry */ |
| | 1710 | entry = get_entry(obj); |
| | 1711 | |
| | 1712 | /* |
| | 1713 | * if the object can have weak references, notify it; if not, |
| | 1714 | * there's no need to do anything |
| | 1715 | */ |
| | 1716 | if (entry->can_have_weak_refs_) |
| | 1717 | entry->get_vm_obj()->remove_stale_undo_weak_ref(vmg_ undo_rec); |
| | 1718 | } |
| | 1719 | |
| | 1720 | /* |
| | 1721 | * Determine if the given object is part of the latest undo savepoint. |
| | 1722 | * Returns true if an undo savepoint has been created since the object |
| | 1723 | * was created, false if not. |
| | 1724 | */ |
| | 1725 | int is_obj_in_undo(vm_obj_id_t obj) const |
| | 1726 | { |
| | 1727 | return (obj != VM_INVALID_OBJ |
| | 1728 | && get_entry(obj)->is_in_undo()); |
| | 1729 | } |
| | 1730 | |
| | 1731 | /* |
| | 1732 | * Determine if a vm_val_t contains a reference to a deletable object. |
| | 1733 | * This is a simple convenience routine. This returns true only if |
| | 1734 | * the value contains a valid object reference, and the object is |
| | 1735 | * currently deletable. |
| | 1736 | */ |
| | 1737 | int is_obj_deletable(const vm_val_t *val) const |
| | 1738 | { |
| | 1739 | return (val->typ == VM_OBJ |
| | 1740 | && val->val.obj != VM_INVALID_OBJ |
| | 1741 | && is_obj_deletable(val->val.obj)); |
| | 1742 | } |
| | 1743 | |
| | 1744 | /* |
| | 1745 | * Determine if the object is saveable. If this returns true, the |
| | 1746 | * object should be saved to a saved state file. If not, the object |
| | 1747 | * should not be included in the saved state file. Note that a |
| | 1748 | * non-saveable object might still be a persistent object - if the |
| | 1749 | * object is a root set object and hasn't been modified since |
| | 1750 | * loading, it's still peristent even though it doesn't get written |
| | 1751 | * to the saved state file. |
| | 1752 | */ |
| | 1753 | int is_obj_saveable(vm_obj_id_t obj) const |
| | 1754 | { |
| | 1755 | return (obj != VM_INVALID_OBJ |
| | 1756 | && get_entry(obj)->is_saveable()); |
| | 1757 | } |
| | 1758 | |
| | 1759 | /* |
| | 1760 | * Determine if the object is persistent in a saved state. If this |
| | 1761 | * returns true, the object will survive saving and restoring the |
| | 1762 | * machine state; if not, the object will not be present after the |
| | 1763 | * machine state is restored. This can be used to test if a weak |
| | 1764 | * reference should be included in a saved state file. |
| | 1765 | */ |
| | 1766 | int is_obj_persistent(vm_obj_id_t obj) const |
| | 1767 | { |
| | 1768 | return (obj != VM_INVALID_OBJ |
| | 1769 | && get_entry(obj)->is_persistent()); |
| | 1770 | } |
| | 1771 | |
| | 1772 | /* determine if the given object is transient */ |
| | 1773 | int is_obj_transient(vm_obj_id_t obj) const |
| | 1774 | { |
| | 1775 | return (obj != VM_INVALID_OBJ |
| | 1776 | && get_entry(obj)->transient_); |
| | 1777 | } |
| | 1778 | |
| | 1779 | /* mark an object as transient */ |
| | 1780 | void set_obj_transient(vm_obj_id_t obj) const |
| | 1781 | { |
| | 1782 | /* set the 'transient' flag in the object */ |
| | 1783 | get_entry(obj)->transient_ = TRUE; |
| | 1784 | } |
| | 1785 | |
| | 1786 | /* set an object's garbage collection characteristics */ |
| | 1787 | void set_obj_gc_characteristics( |
| | 1788 | vm_obj_id_t obj, int can_have_refs, int can_have_weak_refs) const |
| | 1789 | { |
| | 1790 | CVmObjPageEntry *entry; |
| | 1791 | |
| | 1792 | /* get the object's entry */ |
| | 1793 | entry = get_entry(obj); |
| | 1794 | |
| | 1795 | /* set the entry's GC flags as specified */ |
| | 1796 | entry->can_have_refs_ = can_have_refs; |
| | 1797 | entry->can_have_weak_refs_ = can_have_weak_refs; |
| | 1798 | } |
| | 1799 | |
| | 1800 | /* determine if the given object is in the root set */ |
| | 1801 | int is_obj_in_root_set(vm_obj_id_t obj) const |
| | 1802 | { |
| | 1803 | return (obj != VM_INVALID_OBJ |
| | 1804 | && get_entry(obj)->in_root_set_); |
| | 1805 | } |
| | 1806 | |
| | 1807 | /* |
| | 1808 | * Mark the given object as referenced, and recursively mark all of |
| | 1809 | * the objects to which it refers as referenced. |
| | 1810 | */ |
| | 1811 | void mark_all_refs(vm_obj_id_t obj, uint state) |
| | 1812 | { add_to_gc_queue(obj, state); } |
| | 1813 | |
| | 1814 | /* |
| | 1815 | * Receive notification from the undo manager that we're starting a |
| | 1816 | * new savepoint. We'll simply notify all of the objects of this. |
| | 1817 | */ |
| | 1818 | void notify_new_savept(); |
| | 1819 | |
| | 1820 | /* |
| | 1821 | * Apply an undo record |
| | 1822 | */ |
| | 1823 | void apply_undo(VMG_ struct CVmUndoRecord *rec); |
| | 1824 | |
| | 1825 | /* |
| | 1826 | * Rebuild the image file's OBJS blocks for a particular metaclass. |
| | 1827 | * We'll write all of the objects of the given metaclass to one or |
| | 1828 | * more OBJS blocks in the given output file. This can be used to |
| | 1829 | * dump the program state to a new image file after running |
| | 1830 | * 'preinit' or a similar compile-time pre-initialization procedure. |
| | 1831 | * |
| | 1832 | * 'meta_dep_idx' is the index in the metaclass dependency table of |
| | 1833 | * the metaclass to be written. |
| | 1834 | */ |
| | 1835 | void rebuild_image(VMG_ int meta_dep_idx, class CVmImageWriter *writer, |
| | 1836 | class CVmConstMapper *mapper); |
| | 1837 | |
| | 1838 | /* |
| | 1839 | * Scan all objects and add metaclass entries to the metaclass |
| | 1840 | * dependency table for any metaclasses of which there are existing |
| | 1841 | * instances. |
| | 1842 | */ |
| | 1843 | void add_metadeps_for_instances(VMG0_); |
| | 1844 | |
| | 1845 | /* |
| | 1846 | * Scan all active objects and convert objects to constant data |
| | 1847 | * where possible. Certain object metaclasses, such as strings and |
| | 1848 | * lists, can be represented in a rebuilt image file as constant |
| | 1849 | * data; this routine makes all of these conversions. |
| | 1850 | */ |
| | 1851 | void rebuild_image_convert_const_data(VMG_ |
| | 1852 | class CVmConstMapper *const_mapper); |
| | 1853 | |
| | 1854 | /* |
| | 1855 | * Get the maximum object ID that has ever been allocated. This |
| | 1856 | * establishes an upper bound on the object ID's that can be found |
| | 1857 | * among the active objects. |
| | 1858 | */ |
| | 1859 | vm_obj_id_t get_max_used_obj_id() const |
| | 1860 | { return pages_used_ * VM_OBJ_PAGE_CNT; } |
| | 1861 | |
| | 1862 | /* determine if an object ID refers to a valid object */ |
| | 1863 | int is_obj_id_valid(vm_obj_id_t obj) const |
| | 1864 | { |
| | 1865 | /* |
| | 1866 | * the object is valid as long as it's not free, and the ID is |
| | 1867 | * within the valid range |
| | 1868 | */ |
| | 1869 | return (obj != VM_INVALID_OBJ |
| | 1870 | && obj < get_max_used_obj_id() |
| | 1871 | && !get_entry(obj)->free_); |
| | 1872 | } |
| | 1873 | |
| | 1874 | /* |
| | 1875 | * Get the object state. This is intended primarily as a debugging |
| | 1876 | * and testing aid for the VM itself; this value should be of no |
| | 1877 | * interest to normal programs. Returns a value suitable for use |
| | 1878 | * with CVmBifT3Test::get_obj_gc_state(). |
| | 1879 | */ |
| | 1880 | ulong get_obj_internal_state(vm_obj_id_t id) const |
| | 1881 | { |
| | 1882 | /* if the object ID is invalid, return 0xF000 to so indicate */ |
| | 1883 | if (id >= get_max_used_obj_id()) |
| | 1884 | return 0xF000; |
| | 1885 | |
| | 1886 | /* |
| | 1887 | * return the state as a combination of these bits: |
| | 1888 | * |
| | 1889 | * free ? 0 : 1 |
| | 1890 | *. Unreachable=0x00, F-Reachable=0x10, Reachable=0x20 |
| | 1891 | *. Unfinalizable=0x000, Finalizable=0x100, Finalized=0x200 |
| | 1892 | */ |
| | 1893 | return ((get_entry(id)->free_ ? 0 : 1) |
| | 1894 | + (((ulong)get_entry(id)->reachable_) << 4) |
| | 1895 | + (((ulong)get_entry(id)->finalize_state_) << 8)); |
| | 1896 | } |
| | 1897 | |
| | 1898 | /* |
| | 1899 | * Reset to the initial image file state. Discards all objects not |
| | 1900 | * in the root set, skipping finalizers, and resets all objects to |
| | 1901 | * their initial image file state. |
| | 1902 | */ |
| | 1903 | void reset_to_image(VMG0_); |
| | 1904 | |
| | 1905 | /* |
| | 1906 | * Save state to a file. We write out each object's state to the |
| | 1907 | * file so that the state can be restored later. |
| | 1908 | */ |
| | 1909 | void save(VMG_ class CVmFile *fp); |
| | 1910 | |
| | 1911 | /* |
| | 1912 | * Restore state from a previously saved file. Returns zero on |
| | 1913 | * success, or a VMERR_xxx code on failure. |
| | 1914 | * |
| | 1915 | * This routine creates an object fixup table, and returns it in |
| | 1916 | * *fixups. The caller is responsible for deleting this object if a |
| | 1917 | * non-null pointer is returned in *fixups. |
| | 1918 | */ |
| | 1919 | int restore(VMG_ class CVmFile *fp, class CVmObjFixup **fixups); |
| | 1920 | |
| | 1921 | /* |
| | 1922 | * Save an object's image data pointer. An object's load_from_image() |
| | 1923 | * routine may call this routine (it cannot be called from anywhere |
| | 1924 | * else) to save the loaded image location for the object. For each |
| | 1925 | * object that calls this routine, we will call the object's |
| | 1926 | * reload_from_image() method during a reset to initial image file |
| | 1927 | * state. |
| | 1928 | */ |
| | 1929 | void save_image_pointer(vm_obj_id_t obj, const char *ptr, size_t siz); |
| | 1930 | |
| | 1931 | /* |
| | 1932 | * Request post-load initialization. An object can call this to |
| | 1933 | * request that its post_load_init() method be called after an initial |
| | 1934 | * program load, restart, or restore operation. The post_load_init() |
| | 1935 | * method will not be called until the load/restart/restore operation |
| | 1936 | * has loaded every object, so the method can be used to perform any |
| | 1937 | * initialization that depends upon other objects being loaded. |
| | 1938 | */ |
| | 1939 | void request_post_load_init(vm_obj_id_t obj); |
| | 1940 | |
| | 1941 | /* remove a post-load initialization request */ |
| | 1942 | void remove_post_load_init(vm_obj_id_t obj); |
| | 1943 | |
| | 1944 | /* invoke all registered post-load initializations */ |
| | 1945 | void do_all_post_load_init(VMG0_); |
| | 1946 | |
| | 1947 | /* |
| | 1948 | * Ensure that the given object has had its post-load initialization |
| | 1949 | * performed during the current load/restart/restore. If an object's |
| | 1950 | * post-load initialization depends upon another object having already |
| | 1951 | * been initialized, the first object should call this to ensure that |
| | 1952 | * the other object it depends upon has been initialized. This allows |
| | 1953 | * objects to ensure that initialization dependencies are handled in |
| | 1954 | * the correct order, regardless of the order in which the objects were |
| | 1955 | * loaded. |
| | 1956 | * |
| | 1957 | * Post-load initialization is guaranteed to be executed exactly once |
| | 1958 | * per load/restart/restore cycle. When this routine is called, we |
| | 1959 | * check to see if the target object has already been initialized |
| | 1960 | * during this operation, and we do nothing if so. If we do invoke the |
| | 1961 | * target object's post_load_init(), then this will be the only |
| | 1962 | * invocation for this operation. |
| | 1963 | * |
| | 1964 | * Circular dependencies are prohibited. If object A's |
| | 1965 | * post_load_init() method calls this to initialize object B, we will |
| | 1966 | * invoke object B's post_load_init(). If object B in turn calls this |
| | 1967 | * routine to initialize object A, we will observe that object A is |
| | 1968 | * already in the process of being initialized and throw an error. |
| | 1969 | */ |
| | 1970 | void ensure_post_load_init(VMG_ vm_obj_id_t obj); |
| | 1971 | |
| | 1972 | /* |
| | 1973 | * Add an object to the list of machine globals. An object added to |
| | 1974 | * this list will never be deleted. If the object is in the root set |
| | 1975 | * (which means it was loaded from the image file), we will ignore |
| | 1976 | * this request, since a root object is inherently global. |
| | 1977 | */ |
| | 1978 | void add_to_globals(vm_obj_id_t obj); |
| | 1979 | |
| | 1980 | /* |
| | 1981 | * Create a "global variable." A global variable is part of the root |
| | 1982 | * set: any value in a variable allocated here will be traced during |
| | 1983 | * garbage collection. Global variables are meant for use by other |
| | 1984 | * subsystems, so that other subsystems can include their own static |
| | 1985 | * and global variables in the root set. |
| | 1986 | * |
| | 1987 | * Global variables are not affected by RESTORE, RESTART, or UNDO (like |
| | 1988 | * the stack, global variables are transient). |
| | 1989 | * |
| | 1990 | * The caller can use delete_global_var() to delete the variable when |
| | 1991 | * done with it. Any global variable that is never explicitly deleted |
| | 1992 | * will be automatically deleted when the object table itself is |
| | 1993 | * destroyed. |
| | 1994 | */ |
| | 1995 | vm_globalvar_t *create_global_var(); |
| | 1996 | void delete_global_var(vm_globalvar_t *var); |
| | 1997 | |
| | 1998 | private: |
| | 1999 | /* rebuild the image, writing only transient or only persistent objects */ |
| | 2000 | void rebuild_image(VMG_ int meta_dep_idx, CVmImageWriter *writer, |
| | 2001 | class CVmConstMapper *mapper, int trans); |
| | 2002 | |
| | 2003 | /* invoke a post-load initialization method */ |
| | 2004 | static void call_post_load_init(VMG_ class CVmHashEntryPLI *entry); |
| | 2005 | |
| | 2006 | /* enumeration callbacks for post-load initialization */ |
| | 2007 | static void pli_status_cb(void *ctx, class CVmHashEntry *entry); |
| | 2008 | static void pli_invoke_cb(void *ctx, class CVmHashEntry *entry); |
| | 2009 | |
| | 2010 | /* get the page entry for a given ID */ |
| | 2011 | inline CVmObjPageEntry *get_entry(vm_obj_id_t id) const |
| | 2012 | { |
| | 2013 | return &pages_[id >> VM_OBJ_PAGE_CNT_LOG2][id & (VM_OBJ_PAGE_CNT - 1)]; |
| | 2014 | } |
| | 2015 | |
| | 2016 | /* delete an entry */ |
| | 2017 | void delete_entry(VMG_ vm_obj_id_t id, CVmObjPageEntry *entry); |
| | 2018 | |
| | 2019 | /* allocate a new page of objects */ |
| | 2020 | void alloc_new_page(); |
| | 2021 | |
| | 2022 | /* |
| | 2023 | * initialize a newly-allocated object table entry -- removes the |
| | 2024 | * entry from the free list, marks the entry as allocated, marks it |
| | 2025 | * in or out of the root set as appropriate, and initializes its GC |
| | 2026 | * status as appropriate |
| | 2027 | */ |
| | 2028 | void init_entry_for_alloc(vm_obj_id_t id, CVmObjPageEntry *entry, |
| | 2029 | int in_root_set, int can_have_refs, |
| | 2030 | int can_have_weak_refs); |
| | 2031 | |
| | 2032 | /* |
| | 2033 | * Mark an object as referenced for the garbage collector and add it |
| | 2034 | * to the garbage collector's work queue. If the object is already |
| | 2035 | * marked as referenced, this does nothing. |
| | 2036 | */ |
| | 2037 | void add_to_gc_queue(vm_obj_id_t id, uint state) |
| | 2038 | { |
| | 2039 | /* get the object header and add it to the work queue */ |
| | 2040 | add_to_gc_queue(id, get_entry(id), state); |
| | 2041 | } |
| | 2042 | |
| | 2043 | /* add an object to the gc work queue given the object header entry */ |
| | 2044 | void add_to_gc_queue(vm_obj_id_t id, CVmObjPageEntry *entry, uint state) |
| | 2045 | { |
| | 2046 | /* |
| | 2047 | * If it's not already referenced somehow, add it to the queue. |
| | 2048 | * If it's marked as referenced, it's already in the queue (or |
| | 2049 | * it's already been in the queue and it's been processed). |
| | 2050 | * |
| | 2051 | * If the object cannot have references to other objects, don't |
| | 2052 | * add it to the queue - simply elevate its reachability state. |
| | 2053 | * We put objects in the queue in order to trace into the objects |
| | 2054 | * they reference, so an object that can't reference any other |
| | 2055 | * objects doesn't need to be put in the queue. |
| | 2056 | */ |
| | 2057 | if (entry->can_have_refs_ && entry->reachable_ == VMOBJ_UNREACHABLE) |
| | 2058 | { |
| | 2059 | /* add it to the work queue */ |
| | 2060 | entry->next_obj_ = gc_queue_head_; |
| | 2061 | gc_queue_head_ = id; |
| | 2062 | |
| | 2063 | /* |
| | 2064 | * Since the entry is unreachable, and unreachable is the |
| | 2065 | * lowest reachability state, we know that 'state' is at least |
| | 2066 | * as reachable, so we can without further condidtions elevate |
| | 2067 | * the reachability state. (We can thus avoid the extra |
| | 2068 | * comparison we have to do below for other current states.) |
| | 2069 | */ |
| | 2070 | entry->reachable_ = state; |
| | 2071 | } |
| | 2072 | else |
| | 2073 | { |
| | 2074 | /* |
| | 2075 | * Elevate the reachability state. Never reduce an object's |
| | 2076 | * reachability state: Finalizer-Reachable is higher than |
| | 2077 | * Unreachable, and Reachable is higher than |
| | 2078 | * Finalizer-Reachable. |
| | 2079 | * |
| | 2080 | * In other words, if an object is already marked Reachable, |
| | 2081 | * never reduce its state to Finalizer-Reachable just because |
| | 2082 | * we find that it can also be reached from a |
| | 2083 | * Finalizer-Reachable object, when we already know that it |
| | 2084 | * can be reached from a root-set object. |
| | 2085 | */ |
| | 2086 | if (state > entry->reachable_) |
| | 2087 | entry->reachable_ = state; |
| | 2088 | } |
| | 2089 | } |
| | 2090 | |
| | 2091 | /* |
| | 2092 | * Add an object to the finalizer work queue. An object can only be |
| | 2093 | * in one queue - it can't be in both the finalizer queue and the |
| | 2094 | * main gc work queue. |
| | 2095 | */ |
| | 2096 | void add_to_finalize_queue(vm_obj_id_t id, CVmObjPageEntry *entry) |
| | 2097 | { |
| | 2098 | /* mark the object as finalizer-reachable */ |
| | 2099 | entry->reachable_ = VMOBJ_F_REACHABLE; |
| | 2100 | |
| | 2101 | /* link it into the finalize list */ |
| | 2102 | entry->next_obj_ = finalize_queue_head_; |
| | 2103 | finalize_queue_head_ = id; |
| | 2104 | } |
| | 2105 | |
| | 2106 | /* |
| | 2107 | * Count an allocation and check to see if we should run garbage |
| | 2108 | * collection. We run gc after a certain number of consecutive |
| | 2109 | * allocations to ensure that allocation-intensive operations don't |
| | 2110 | * fill up memory if we can avoid it by removing unreferenced |
| | 2111 | * objects. |
| | 2112 | */ |
| | 2113 | void alloc_check_gc(VMG_ int do_count) |
| | 2114 | { |
| | 2115 | /* count the allocation if desired */ |
| | 2116 | if (do_count) |
| | 2117 | ++allocs_since_gc_; |
| | 2118 | |
| | 2119 | /* if we've passed our threshhold for collecting garbage, do so now */ |
| | 2120 | if (gc_enabled_ && allocs_since_gc_ > max_allocs_between_gc_) |
| | 2121 | gc_before_alloc(vmg0_); |
| | 2122 | } |
| | 2123 | |
| | 2124 | /* |
| | 2125 | * Run a garbage collection in preparation to allocate memory. Runs |
| | 2126 | * one garbage collection pass then one finalizer pass. |
| | 2127 | */ |
| | 2128 | void gc_before_alloc(VMG0_); |
| | 2129 | |
| | 2130 | /* garbage collection: trace objects reachable from the stack */ |
| | 2131 | void gc_trace_stack(VMG0_); |
| | 2132 | |
| | 2133 | /* garbage collection: trace objects reachable from the imports */ |
| | 2134 | void gc_trace_imports(VMG0_); |
| | 2135 | |
| | 2136 | /* garbage collection: trace objects reachable from machine globals */ |
| | 2137 | void gc_trace_globals(VMG0_); |
| | 2138 | |
| | 2139 | /* garbage collection: trace all objects reachable from the work queue */ |
| | 2140 | void gc_trace_work_queue(VMG_ int trace_transient); |
| | 2141 | |
| | 2142 | /* continue a GC pass */ |
| | 2143 | int gc_pass_continue(VMG_ int trace_transient); |
| | 2144 | |
| | 2145 | /* |
| | 2146 | * set the initial GC conditions for an object -- this puts the |
| | 2147 | * object into the appropriate queue and sets the appropriate |
| | 2148 | * reachability state in preparation for the start of the next GC |
| | 2149 | * pass |
| | 2150 | */ |
| | 2151 | void gc_set_init_conditions(vm_obj_id_t id, |
| | 2152 | struct CVmObjPageEntry *entry) |
| | 2153 | { |
| | 2154 | /* |
| | 2155 | * Mark the object as unreachable -- at the start of each GC pass, |
| | 2156 | * all non-root-set objects must be marked unreachable. (It |
| | 2157 | * doesn't matter how we mark root set objects, so we simply mark |
| | 2158 | * everything as reachable to avoid an unnecessary test.) |
| | 2159 | */ |
| | 2160 | entry->reachable_ = VMOBJ_UNREACHABLE; |
| | 2161 | |
| | 2162 | /* |
| | 2163 | * If it's in the root set, add it to the GC work queue -- all |
| | 2164 | * root-set objects must be in the work queue and marked as |
| | 2165 | * reachable at the start of each GC pass. |
| | 2166 | */ |
| | 2167 | if (entry->in_root_set_) |
| | 2168 | add_to_gc_queue(id, entry, VMOBJ_REACHABLE); |
| | 2169 | } |
| | 2170 | |
| | 2171 | /* hash table of objects requested post_load_init() service */ |
| | 2172 | class CVmHashTable *post_load_init_table_; |
| | 2173 | |
| | 2174 | /* |
| | 2175 | * Head of global object page list. The global objects are objects |
| | 2176 | * that are always reachable but which weren't necessarily loaded from |
| | 2177 | * the image file; these objects must be treated as dynamically |
| | 2178 | * created for purposes such as saving, but must never be deleted as |
| | 2179 | * long as they are globally reachable. |
| | 2180 | */ |
| | 2181 | CVmObjGlobPage *globals_; |
| | 2182 | |
| | 2183 | /* head of global variable list */ |
| | 2184 | struct vm_globalvar_t *global_var_head_; |
| | 2185 | |
| | 2186 | /* |
| | 2187 | * Master page table. This is an array of pointers to pages. Each |
| | 2188 | * page contains a fixed number of slots for fixed-size parts. |
| | 2189 | */ |
| | 2190 | CVmObjPageEntry **pages_; |
| | 2191 | |
| | 2192 | /* number of page slots allocated, and the number actually used */ |
| | 2193 | size_t page_slots_; |
| | 2194 | size_t pages_used_; |
| | 2195 | |
| | 2196 | /* first free object */ |
| | 2197 | vm_obj_id_t first_free_; |
| | 2198 | |
| | 2199 | /* first page of saved image data pointers */ |
| | 2200 | struct vm_image_ptr_page *image_ptr_head_; |
| | 2201 | |
| | 2202 | /* last page of saved image data pointers */ |
| | 2203 | struct vm_image_ptr_page *image_ptr_tail_; |
| | 2204 | |
| | 2205 | /* number of image data pointers stored on last image pointer page */ |
| | 2206 | size_t image_ptr_last_cnt_; |
| | 2207 | |
| | 2208 | /* head of garbage collection work queue */ |
| | 2209 | vm_obj_id_t gc_queue_head_; |
| | 2210 | |
| | 2211 | /* head of finalizer queue */ |
| | 2212 | vm_obj_id_t finalize_queue_head_; |
| | 2213 | |
| | 2214 | /* |
| | 2215 | * Allocations since last garbage collection. We increment this on |
| | 2216 | * each allocation, and reset it to zero each time we collect |
| | 2217 | * garbage. When we've performed too many allocations since the |
| | 2218 | * last garbage collection, we force a gc pass. |
| | 2219 | */ |
| | 2220 | uint allocs_since_gc_; |
| | 2221 | |
| | 2222 | /* |
| | 2223 | * Maximum number of allocations before we run the garbage |
| | 2224 | * collector. |
| | 2225 | */ |
| | 2226 | uint max_allocs_between_gc_; |
| | 2227 | |
| | 2228 | /* garbage collection enabled */ |
| | 2229 | uint gc_enabled_ : 1; |
| | 2230 | }; |
| | 2231 | |
| | 2232 | /* ------------------------------------------------------------------------ */ |
| | 2233 | /* |
| | 2234 | * An image data pointer. The object table uses this structure to save |
| | 2235 | * the image data location for a given object when the object requests |
| | 2236 | * that this information be saved. |
| | 2237 | */ |
| | 2238 | struct vm_image_ptr |
| | 2239 | { |
| | 2240 | /* object ID */ |
| | 2241 | vm_obj_id_t obj_id_; |
| | 2242 | |
| | 2243 | /* pointer to image data and length of the data */ |
| | 2244 | const char *image_data_ptr_; |
| | 2245 | size_t image_data_len_; |
| | 2246 | }; |
| | 2247 | |
| | 2248 | /* |
| | 2249 | * Maximum number of image pointers stored per image pointer page |
| | 2250 | */ |
| | 2251 | const size_t VM_IMAGE_PTRS_PER_PAGE = 400; |
| | 2252 | |
| | 2253 | /* |
| | 2254 | * A page of image pointers. |
| | 2255 | */ |
| | 2256 | struct vm_image_ptr_page |
| | 2257 | { |
| | 2258 | /* next page in the list of pages */ |
| | 2259 | vm_image_ptr_page *next_; |
| | 2260 | |
| | 2261 | /* array of image pointers */ |
| | 2262 | vm_image_ptr ptrs_[VM_IMAGE_PTRS_PER_PAGE]; |
| | 2263 | }; |
| | 2264 | |
| | 2265 | |
| | 2266 | /* ------------------------------------------------------------------------ */ |
| | 2267 | /* |
| | 2268 | * The variable-size parts are stored in a heap separately from the object |
| | 2269 | * headers. Because the variable-size objects can expand or contract |
| | 2270 | * dynamically, objects in the heap can move to new addresses. |
| | 2271 | * |
| | 2272 | * A particular block of memory in the heap is referenced by zero or one |
| | 2273 | * object at any given time; a heap block is never shared among multiple |
| | 2274 | * objects. Furthermore, the only thing that can directly reference a |
| | 2275 | * heap block is an object's fixed portion (through its extension |
| | 2276 | * pointer). Hence, we manage the variable heap directly through the |
| | 2277 | * object headers: when an object becomes unreferenced, we explicitly free |
| | 2278 | * the associated variable part. |
| | 2279 | * |
| | 2280 | * It is possible for a single object to allocate more than one heap |
| | 2281 | * block. In practice, most objects will allocate only one heap block (if |
| | 2282 | * they allocate a heap block at all), but there is no reason an object |
| | 2283 | * can't allocate more than one block. |
| | 2284 | * |
| | 2285 | * Note that polymorphism in the variable parts is handled via the fixed |
| | 2286 | * part. Because the fixed part is responsible for interactions with the |
| | 2287 | * variable part, each fixed part implementation will be mated to a |
| | 2288 | * particular variable part implementation. These implementations may |
| | 2289 | * themselves be polymorphic, of course, but this isn't directly necessary |
| | 2290 | * in the base class. |
| | 2291 | */ |
| | 2292 | |
| | 2293 | /* |
| | 2294 | * Variable-size object heap interface. |
| | 2295 | */ |
| | 2296 | class CVmVarHeap |
| | 2297 | { |
| | 2298 | public: |
| | 2299 | virtual ~CVmVarHeap() { } |
| | 2300 | |
| | 2301 | /* |
| | 2302 | * Initialize. The global object table is valid at this point, and |
| | 2303 | * will remain valid until after terminate() is called. |
| | 2304 | */ |
| | 2305 | virtual void init(VMG0_) = 0; |
| | 2306 | |
| | 2307 | /* |
| | 2308 | * Terminate. The global object table will remain valid until after |
| | 2309 | * this function returns. |
| | 2310 | * |
| | 2311 | * The object table is expected to free each object's variable part |
| | 2312 | * explicitly, so this function need not deallocate the memory used |
| | 2313 | * by variable parts. |
| | 2314 | */ |
| | 2315 | virtual void terminate() = 0; |
| | 2316 | |
| | 2317 | /* |
| | 2318 | * Allocate a variable-size part. 'siz' is the size requested in |
| | 2319 | * bytes, and 'obj' is a pointer to the object header. The object |
| | 2320 | * header will never move in memory, so this pointer is valid for as |
| | 2321 | * long as the object remains allocated, hence the heap manager can |
| | 2322 | * store the header pointer with the memory block if desired. |
| | 2323 | */ |
| | 2324 | virtual void *alloc_mem(size_t siz, CVmObject *obj) = 0; |
| | 2325 | |
| | 2326 | /* |
| | 2327 | * Resize a variable-size part. The 'siz' is the new size requested |
| | 2328 | * in bytes, and 'varpart' is the old variable-size memory block. |
| | 2329 | * This should return a new variable-size memory block containing a |
| | 2330 | * copy of the data in the original block, but the block should be |
| | 2331 | * resized to at least 'siz' bytes. Returns a pointer to the new |
| | 2332 | * block, which may move to a new memory location. |
| | 2333 | * |
| | 2334 | * If we move the memory to a new location, we are responsible for |
| | 2335 | * freeing the old block of memory that the variable part occupied, |
| | 2336 | * if necessary. |
| | 2337 | * |
| | 2338 | * We do not need to worry about informing the object header of any |
| | 2339 | * change to the address of the variable part; the caller is |
| | 2340 | * responsible for making any necessary changes in the object header |
| | 2341 | * based on our return value. |
| | 2342 | */ |
| | 2343 | virtual void *realloc_mem(size_t siz, void *varpart, |
| | 2344 | CVmObject *obj) = 0; |
| | 2345 | |
| | 2346 | /* |
| | 2347 | * Free an object in the heap. The object header may no longer be |
| | 2348 | * valid after this function returns, so the heap manager should not |
| | 2349 | * store the object header pointer after this function returns. |
| | 2350 | */ |
| | 2351 | virtual void free_mem(void *varpart) = 0; |
| | 2352 | |
| | 2353 | #if 0 |
| | 2354 | /* |
| | 2355 | * This is not currently used by the heap implementation (and doesn't |
| | 2356 | * even have any theoretical reason to exist at the moment, since the |
| | 2357 | * adoption of a non-moveable heap policy and the removal of |
| | 2358 | * move_var_part()), so it's been removed to avoid the unnecessary |
| | 2359 | * overhead of calling an empty method. |
| | 2360 | */ |
| | 2361 | |
| | 2362 | /* |
| | 2363 | * Receive notification of the completion of a garbage collection |
| | 2364 | * pass. If the heap manager is capable of closing gaps in memory |
| | 2365 | * by moving objects around, this is a good time to perform this |
| | 2366 | * work, since we have just deleted all unreachable objects. |
| | 2367 | * |
| | 2368 | * If any object moves during processing here, we must call the |
| | 2369 | * associated CVmObject's move_var_part() routine to tell it about |
| | 2370 | * its new location. |
| | 2371 | * |
| | 2372 | * This routine isn't required to do anything at all. It's simply |
| | 2373 | * provided as a notification for heap managers that can take |
| | 2374 | * advantage of the opportunity to compact the heap. |
| | 2375 | */ |
| | 2376 | virtual void finish_gc_pass() = 0; |
| | 2377 | #endif |
| | 2378 | }; |
| | 2379 | |
| | 2380 | |
| | 2381 | /* ------------------------------------------------------------------------ */ |
| | 2382 | /* |
| | 2383 | * Simple variable-size object heap implementation. This implementation |
| | 2384 | * uses the normal C heap manager (malloc and free) to manage the heap. |
| | 2385 | */ |
| | 2386 | |
| | 2387 | /* |
| | 2388 | * block header - we use the header to keep track of the size of the |
| | 2389 | * object's data area |
| | 2390 | */ |
| | 2391 | struct CVmVarHeapMallocHdr |
| | 2392 | { |
| | 2393 | /* size of the object */ |
| | 2394 | size_t siz_; |
| | 2395 | }; |
| | 2396 | |
| | 2397 | /* |
| | 2398 | * heap implementation |
| | 2399 | */ |
| | 2400 | class CVmVarHeapMalloc: public CVmVarHeap |
| | 2401 | { |
| | 2402 | public: |
| | 2403 | CVmVarHeapMalloc() { } |
| | 2404 | ~CVmVarHeapMalloc() { } |
| | 2405 | |
| | 2406 | /* initialize */ |
| | 2407 | void init(VMG0_) { } |
| | 2408 | |
| | 2409 | /* terminate */ |
| | 2410 | void terminate() { } |
| | 2411 | |
| | 2412 | /* allocate memory */ |
| | 2413 | void *alloc_mem(size_t siz, CVmObject *) |
| | 2414 | { |
| | 2415 | CVmVarHeapMallocHdr *hdr; |
| | 2416 | |
| | 2417 | /* allocate space for the block plus the header */ |
| | 2418 | hdr = (CVmVarHeapMallocHdr *) |
| | 2419 | t3malloc(siz + sizeof(CVmVarHeapMallocHdr)); |
| | 2420 | |
| | 2421 | /* set up the header */ |
| | 2422 | hdr->siz_ = siz; |
| | 2423 | |
| | 2424 | /* return the start of the part immediately after the header */ |
| | 2425 | return (void *)(hdr + 1); |
| | 2426 | } |
| | 2427 | |
| | 2428 | /* reallocate memory */ |
| | 2429 | void *realloc_mem(size_t siz, void *varpart, CVmObject *) |
| | 2430 | { |
| | 2431 | CVmVarHeapMallocHdr *hdr; |
| | 2432 | |
| | 2433 | /* |
| | 2434 | * get the original header, which immediately precedes the |
| | 2435 | * original variable part in memory |
| | 2436 | */ |
| | 2437 | hdr = ((CVmVarHeapMallocHdr *)varpart) - 1; |
| | 2438 | |
| | 2439 | /* |
| | 2440 | * Reallocate it - the header is the actual memory block as far |
| | 2441 | * as malloc was concerned, so realloc that. Note that we must |
| | 2442 | * add in the space needed for our header in the resized block. |
| | 2443 | */ |
| | 2444 | hdr = (CVmVarHeapMallocHdr *) |
| | 2445 | t3realloc(hdr, siz + sizeof(CVmVarHeapMallocHdr)); |
| | 2446 | |
| | 2447 | /* adjust the size of the block in the header */ |
| | 2448 | hdr->siz_ = siz; |
| | 2449 | |
| | 2450 | /* return the part immediately after the header */ |
| | 2451 | return (void *)(hdr + 1); |
| | 2452 | } |
| | 2453 | |
| | 2454 | /* free memory */ |
| | 2455 | void free_mem(void *varpart) |
| | 2456 | { |
| | 2457 | CVmVarHeapMallocHdr *hdr; |
| | 2458 | |
| | 2459 | /* |
| | 2460 | * get the original header, which immediately precedes the |
| | 2461 | * original variable part in memory |
| | 2462 | */ |
| | 2463 | hdr = ((CVmVarHeapMallocHdr *)varpart) - 1; |
| | 2464 | |
| | 2465 | /* |
| | 2466 | * free the header, which is the actual memory block as far as |
| | 2467 | * malloc was concerned |
| | 2468 | */ |
| | 2469 | t3free(hdr); |
| | 2470 | } |
| | 2471 | |
| | 2472 | #if 0 |
| | 2473 | /* removed with the removal of move_var_part() */ |
| | 2474 | |
| | 2475 | /* |
| | 2476 | * complete garbage collection pass - we don't have to do anything |
| | 2477 | * here, since we can't move objects around to consolidate free |
| | 2478 | * space |
| | 2479 | */ |
| | 2480 | void finish_gc_pass() { } |
| | 2481 | #endif |
| | 2482 | |
| | 2483 | private: |
| | 2484 | }; |
| | 2485 | |
| | 2486 | /* ------------------------------------------------------------------------ */ |
| | 2487 | /* |
| | 2488 | * Hybrid cell-based and malloc-based heap allocator. This heap manager |
| | 2489 | * uses arrays of fixed-size blocks to allocate small objects, and falls |
| | 2490 | * back on malloc for allocating large objects. Small-block allocations |
| | 2491 | * and frees are fast, require very little memory overhead, and minimize |
| | 2492 | * heap fragmentation by packing large blocks of fixed-size items into |
| | 2493 | * arrays, then suballocating out of free lists built from the arrays. |
| | 2494 | */ |
| | 2495 | |
| | 2496 | /* |
| | 2497 | * Each item we allocate from a small-object array has a header that |
| | 2498 | * points back to the array's master list. This is necessary so that we |
| | 2499 | * can put the object back in the appropriate free list when it is |
| | 2500 | * deleted. |
| | 2501 | */ |
| | 2502 | struct CVmVarHeapHybrid_hdr |
| | 2503 | { |
| | 2504 | /* |
| | 2505 | * the block interface that allocated this object -- we use this |
| | 2506 | * when we free the object so that we can call the free() routine in |
| | 2507 | * the block manager that originally did the allocation |
| | 2508 | */ |
| | 2509 | class CVmVarHeapHybrid_block *block; |
| | 2510 | }; |
| | 2511 | |
| | 2512 | /* |
| | 2513 | * Hybrid heap allocator - sub-block interface. Each small-object cell |
| | 2514 | * list is represented by one of these objects, as is the fallback |
| | 2515 | * malloc allocator. |
| | 2516 | */ |
| | 2517 | class CVmVarHeapHybrid_block |
| | 2518 | { |
| | 2519 | public: |
| | 2520 | /* allocate memory */ |
| | 2521 | virtual struct CVmVarHeapHybrid_hdr *alloc(size_t siz) = 0; |
| | 2522 | |
| | 2523 | /* free memory */ |
| | 2524 | virtual void free(struct CVmVarHeapHybrid_hdr *) = 0; |
| | 2525 | |
| | 2526 | /* |
| | 2527 | * Reallocate memory. If necessary, allocate new memory, copy the |
| | 2528 | * data to the new memory, and delete the old memory. We receive |
| | 2529 | * the heap manager as an argument so that we can call it to |
| | 2530 | * allocate new memory if necessary. |
| | 2531 | */ |
| | 2532 | virtual void *realloc(struct CVmVarHeapHybrid_hdr *mem, size_t siz, |
| | 2533 | class CVmObject *obj) = 0; |
| | 2534 | }; |
| | 2535 | |
| | 2536 | /* |
| | 2537 | * Malloc suballocator |
| | 2538 | */ |
| | 2539 | class CVmVarHeapHybrid_malloc: public CVmVarHeapHybrid_block |
| | 2540 | { |
| | 2541 | public: |
| | 2542 | /* allocate memory */ |
| | 2543 | virtual struct CVmVarHeapHybrid_hdr *alloc(size_t siz) |
| | 2544 | { |
| | 2545 | CVmVarHeapHybrid_hdr *ptr; |
| | 2546 | |
| | 2547 | /* adjust the size to add in the required header */ |
| | 2548 | siz = osrndsz(siz + sizeof(CVmVarHeapHybrid_hdr)); |
| | 2549 | |
| | 2550 | /* allocate directly via the default system heap manager */ |
| | 2551 | ptr = (CVmVarHeapHybrid_hdr *)t3malloc(siz); |
| | 2552 | |
| | 2553 | /* fill in the header */ |
| | 2554 | ptr->block = this; |
| | 2555 | |
| | 2556 | /* return the new block */ |
| | 2557 | return ptr; |
| | 2558 | } |
| | 2559 | |
| | 2560 | /* release memory */ |
| | 2561 | virtual void free(CVmVarHeapHybrid_hdr *mem) |
| | 2562 | { |
| | 2563 | /* release the memory directly to the default system heap manager */ |
| | 2564 | t3free(mem); |
| | 2565 | } |
| | 2566 | |
| | 2567 | /* reallocate memory */ |
| | 2568 | virtual void *realloc(struct CVmVarHeapHybrid_hdr *mem, size_t siz, |
| | 2569 | CVmObject *) |
| | 2570 | { |
| | 2571 | CVmVarHeapHybrid_hdr *ptr; |
| | 2572 | |
| | 2573 | /* adjust the new size to add in the required header */ |
| | 2574 | siz = osrndsz(siz + sizeof(CVmVarHeapHybrid_hdr)); |
| | 2575 | |
| | 2576 | /* reallocate the block */ |
| | 2577 | ptr = (CVmVarHeapHybrid_hdr *)t3realloc(mem, siz); |
| | 2578 | |
| | 2579 | /* fill in the header in the new block */ |
| | 2580 | ptr->block = this; |
| | 2581 | |
| | 2582 | /* return the caller-visible part of the new block */ |
| | 2583 | return (void *)(ptr + 1); |
| | 2584 | } |
| | 2585 | }; |
| | 2586 | |
| | 2587 | /* |
| | 2588 | * Small-object array list head. We suballocate small objects from |
| | 2589 | * these arrays. We maintain one of these objects for each distinct |
| | 2590 | * cell size; this object manages all of the storage for blocks of the |
| | 2591 | * cell size. |
| | 2592 | */ |
| | 2593 | class CVmVarHeapHybrid_head: public CVmVarHeapHybrid_block |
| | 2594 | { |
| | 2595 | public: |
| | 2596 | CVmVarHeapHybrid_head(class CVmVarHeapHybrid *mem_mgr, |
| | 2597 | size_t cell_size, size_t page_count) |
| | 2598 | { |
| | 2599 | /* remember our memory manager */ |
| | 2600 | mem_mgr_ = mem_mgr; |
| | 2601 | |
| | 2602 | /* remember our cell size and number of items per array */ |
| | 2603 | cell_size_ = cell_size; |
| | 2604 | page_count_ = page_count; |
| | 2605 | |
| | 2606 | /* we have nothing in our free list yet */ |
| | 2607 | first_free_ = 0; |
| | 2608 | } |
| | 2609 | |
| | 2610 | /* allocate an object from my pool, expanding the pool if necessary */ |
| | 2611 | CVmVarHeapHybrid_hdr *alloc(size_t siz); |
| | 2612 | |
| | 2613 | /* free a cell */ |
| | 2614 | void free(CVmVarHeapHybrid_hdr *mem); |
| | 2615 | |
| | 2616 | /* reallocate */ |
| | 2617 | virtual void *realloc(struct CVmVarHeapHybrid_hdr *mem, size_t siz, |
| | 2618 | class CVmObject *obj); |
| | 2619 | |
| | 2620 | /* get the cell size for this cell manager */ |
| | 2621 | size_t get_cell_size() const { return cell_size_; } |
| | 2622 | |
| | 2623 | private: |
| | 2624 | /* size of each cell in the array */ |
| | 2625 | size_t cell_size_; |
| | 2626 | |
| | 2627 | /* number of items we allocate per array */ |
| | 2628 | size_t page_count_; |
| | 2629 | |
| | 2630 | /* head of the free list of cells in this array */ |
| | 2631 | void *first_free_; |
| | 2632 | |
| | 2633 | /* our memory manager */ |
| | 2634 | CVmVarHeapHybrid *mem_mgr_; |
| | 2635 | }; |
| | 2636 | |
| | 2637 | /* |
| | 2638 | * Small-object array list block. We dynamically allocate these array |
| | 2639 | * blocks as needed to hold blocks of a particular size. |
| | 2640 | */ |
| | 2641 | struct CVmVarHeapHybrid_array |
| | 2642 | { |
| | 2643 | /* next array in the master list */ |
| | 2644 | CVmVarHeapHybrid_array *next_array; |
| | 2645 | |
| | 2646 | /* |
| | 2647 | * memory for allocation (we over-allocate the structure to make |
| | 2648 | * room for some number of our fixed-size cells) |
| | 2649 | */ |
| | 2650 | char mem[1]; |
| | 2651 | }; |
| | 2652 | |
| | 2653 | /* |
| | 2654 | * heap implementation |
| | 2655 | */ |
| | 2656 | class CVmVarHeapHybrid: public CVmVarHeap |
| | 2657 | { |
| | 2658 | friend class CVmVarHeapHybrid_head; |
| | 2659 | |
| | 2660 | public: |
| | 2661 | CVmVarHeapHybrid(); |
| | 2662 | ~CVmVarHeapHybrid(); |
| | 2663 | |
| | 2664 | /* initialize */ |
| | 2665 | void init(VMG0_) { } |
| | 2666 | |
| | 2667 | /* terminate */ |
| | 2668 | void terminate() { } |
| | 2669 | |
| | 2670 | /* allocate memory */ |
| | 2671 | void *alloc_mem(size_t siz, CVmObject *obj); |
| | 2672 | |
| | 2673 | /* reallocate memory */ |
| | 2674 | void *realloc_mem(size_t siz, void *varpart, CVmObject *obj); |
| | 2675 | |
| | 2676 | /* free memory */ |
| | 2677 | void free_mem(void *varpart); |
| | 2678 | |
| | 2679 | #if 0 |
| | 2680 | /* removed with the removal of move_var_part() */ |
| | 2681 | |
| | 2682 | /* |
| | 2683 | * complete garbage collection pass - we don't have to do anything |
| | 2684 | * here, since we can't move objects around to consolidate free |
| | 2685 | * space |
| | 2686 | */ |
| | 2687 | void finish_gc_pass() { } |
| | 2688 | #endif |
| | 2689 | |
| | 2690 | private: |
| | 2691 | /* |
| | 2692 | * Head of list of arrays. We keep this list so that we can delete |
| | 2693 | * all of the arrays when we delete this heap manager object itself. |
| | 2694 | */ |
| | 2695 | CVmVarHeapHybrid_array *first_array_; |
| | 2696 | |
| | 2697 | /* |
| | 2698 | * Array of cell-based subheap managers. This array will be ordered |
| | 2699 | * from smallest to largest, so we can search it for the best fit to |
| | 2700 | * a requested size. |
| | 2701 | */ |
| | 2702 | CVmVarHeapHybrid_head **cell_heaps_; |
| | 2703 | |
| | 2704 | /* number of cell heap managers */ |
| | 2705 | size_t cell_heap_cnt_; |
| | 2706 | |
| | 2707 | /* |
| | 2708 | * Our fallback malloc heap manager. We'll use this allocator for |
| | 2709 | * any blocks that we can't allocate from one of our cell-based |
| | 2710 | * memory managers. |
| | 2711 | */ |
| | 2712 | CVmVarHeapHybrid_malloc *malloc_heap_; |
| | 2713 | }; |
| | 2714 | |
| | 2715 | /* ------------------------------------------------------------------------ */ |
| | 2716 | /* |
| | 2717 | * Memory Manager - this is the primary interface to the object memory |
| | 2718 | * subsystem. |
| | 2719 | */ |
| | 2720 | class CVmMemory |
| | 2721 | { |
| | 2722 | public: |
| | 2723 | /* create the memory manager, using a given variable-size heap */ |
| | 2724 | CVmMemory(VMG_ CVmVarHeap *varheap); |
| | 2725 | |
| | 2726 | /* delete the memory manager */ |
| | 2727 | ~CVmMemory() |
| | 2728 | { |
| | 2729 | /* tell the variable-size heap to disengage */ |
| | 2730 | varheap_->terminate(); |
| | 2731 | } |
| | 2732 | |
| | 2733 | /* get the variable heap manager */ |
| | 2734 | CVmVarHeap *get_var_heap() const { return varheap_; } |
| | 2735 | |
| | 2736 | private: |
| | 2737 | /* variable-size object heap */ |
| | 2738 | CVmVarHeap *varheap_; |
| | 2739 | |
| | 2740 | /* our constant pool manager */ |
| | 2741 | class CVmPool *constant_pool_; |
| | 2742 | }; |
| | 2743 | |
| | 2744 | /* ------------------------------------------------------------------------ */ |
| | 2745 | /* |
| | 2746 | * Allocate a new object ID |
| | 2747 | */ |
| | 2748 | inline vm_obj_id_t vm_new_id(VMG_ int in_root_set) |
| | 2749 | { |
| | 2750 | /* ask the global object table to allocate a new ID */ |
| | 2751 | return G_obj_table->alloc_obj(vmg_ in_root_set); |
| | 2752 | } |
| | 2753 | |
| | 2754 | /* |
| | 2755 | * Allocate a new object ID, setting GC characteristics |
| | 2756 | */ |
| | 2757 | inline vm_obj_id_t vm_new_id(VMG_ int in_root_set, int can_have_refs, |
| | 2758 | int can_have_weak_refs) |
| | 2759 | { |
| | 2760 | /* ask the global object table to allocate a new ID */ |
| | 2761 | return G_obj_table->alloc_obj(vmg_ in_root_set, can_have_refs, |
| | 2762 | can_have_weak_refs); |
| | 2763 | } |
| | 2764 | |
| | 2765 | /* |
| | 2766 | * Given an object ID, get a pointer to the object |
| | 2767 | */ |
| | 2768 | inline CVmObject *vm_objp(VMG_ vm_obj_id_t id) |
| | 2769 | { |
| | 2770 | /* ask the global object table to translate the ID */ |
| | 2771 | return G_obj_table->get_obj(id); |
| | 2772 | } |
| | 2773 | |
| | 2774 | #endif /* VMOBJ_H */ |
| | 2775 | |
| | 2776 | /* |
| | 2777 | * Register the root object class |
| | 2778 | */ |
| | 2779 | VM_REGISTER_METACLASS(CVmObject) |