Norimasa Okamoto
/
pymite
python-on-a-chip online compiler
Embed:
(wiki syntax)
Show/hide line numbers
heap.c
Go to the documentation of this file.
00001 /* 00002 # This file is Copyright 2002 Dean Hall. 00003 # This file is part of the PyMite VM. 00004 # This file is licensed under the MIT License. 00005 # See the LICENSE file for details. 00006 */ 00007 00008 00009 #undef __FILE_ID__ 00010 #define __FILE_ID__ 0x06 00011 00012 00013 /** 00014 * \file 00015 * \brief VM Heap 00016 * 00017 * VM heap operations. 00018 * All of PyMite's dynamic memory is obtained from this heap. 00019 * The heap provides dynamic memory on demand. 00020 */ 00021 00022 00023 #include "pm.h" 00024 00025 00026 /** The size of the temporary roots stack */ 00027 #define HEAP_NUM_TEMP_ROOTS 24 00028 00029 /** 00030 * The maximum size a live chunk can be (a live chunk is one that is in use). 00031 * The live chunk size is determined by the size field in the *object* 00032 * descriptor. That field is nine bits with two assumed lsbs (zeros): 00033 * (0x1FF << 2) == 2044 00034 */ 00035 #ifdef PM_PLAT_POINTER_SIZE 00036 #if PM_PLAT_POINTER_SIZE == 8 00037 #define HEAP_MAX_LIVE_CHUNK_SIZE 2040 00038 #else 00039 #define HEAP_MAX_LIVE_CHUNK_SIZE 2044 00040 #endif 00041 #endif 00042 00043 /** 00044 * The maximum size a free chunk can be (a free chunk is one that is not in use). 00045 * The free chunk size is limited by the size field in the *heap* descriptor. 00046 * That field is fourteen bits with two assumed least significant bits (zeros): 00047 * (0x3FFF << 2) == 65532 00048 * For 64-bit platforms, the value is 4 bytes less so that a max-sized chunk is 00049 * a multiple of 8, so that max-sized chunks created during heap_init() have a 00050 * good boundary value. 00051 */ 00052 #ifdef PM_PLAT_POINTER_SIZE 00053 #if PM_PLAT_POINTER_SIZE == 8 00054 #define HEAP_MAX_FREE_CHUNK_SIZE 65528 00055 #else 00056 #define HEAP_MAX_FREE_CHUNK_SIZE 65532 00057 #endif 00058 #endif 00059 00060 /** The minimum size a chunk can be 00061 * (rounded up to a multiple of platform-pointer-size) */ 00062 #ifdef PM_PLAT_POINTER_SIZE 00063 #if PM_PLAT_POINTER_SIZE == 8 00064 #define HEAP_MIN_CHUNK_SIZE ((sizeof(PmHeapDesc_t) + 7) & ~7) 00065 #else 00066 #define HEAP_MIN_CHUNK_SIZE ((sizeof(PmHeapDesc_t) + 3) & ~3) 00067 #endif 00068 #endif 00069 00070 00071 00072 /** 00073 * Gets the GC's mark bit for the object. 00074 * This MUST NOT be called on objects that are free. 00075 */ 00076 #define OBJ_GET_GCVAL(pobj) (((pPmObj_t)pobj)->od & OD_MARK_MASK) 00077 00078 /** 00079 * Sets the GC's mark bit for the object 00080 * This MUST NOT be called on objects that are free. 00081 */ 00082 #ifdef HAVE_GC 00083 #define OBJ_SET_GCVAL(pobj, gcval) \ 00084 do \ 00085 { \ 00086 ((pPmObj_t)pobj)->od = (gcval) ? ((pPmObj_t)pobj)->od | OD_MARK_MASK \ 00087 : ((pPmObj_t)pobj)->od & ~OD_MARK_MASK;\ 00088 } \ 00089 while (0) 00090 #else 00091 #define OBJ_SET_GCVAL(pobj, gcval) 00092 #endif /* HAVE_GC */ 00093 00094 #define CHUNK_GET_SIZE(pchunk) (((pPmHeapDesc_t)pchunk)->hd & HD_SIZE_MASK) 00095 00096 /** Sets the size of the chunk in bytes. */ 00097 #define CHUNK_SET_SIZE(pchunk, size) \ 00098 do \ 00099 { \ 00100 ((pPmHeapDesc_t)(pchunk))->hd &= ~HD_SIZE_MASK; \ 00101 ((pPmHeapDesc_t)(pchunk))->hd |= ((size) & HD_SIZE_MASK); \ 00102 } \ 00103 while (0) 00104 00105 #define OBJ_SET_SIZE(pobj, size) \ 00106 do \ 00107 { \ 00108 ((pPmObj_t)pobj)->od &= ~OD_SIZE_MASK; \ 00109 ((pPmObj_t)pobj)->od |= ((size) & OD_SIZE_MASK); \ 00110 } \ 00111 while (0) 00112 00113 00114 /** 00115 * The following is a diagram of the heap descriptor at the head of the chunk: 00116 * @verbatim 00117 * MSb LSb 00118 * 7 6 5 4 3 2 1 0 00119 * pchunk-> +-+-+-+-+-+-+-+-+ S := Size of the chunk (2 LSbs dropped) 00120 * | S |F|R| F := Chunk free bit (not in use) 00121 * +-----------+-+-+ R := Bit reserved for future use 00122 * | S | 00123 * +---------------+ 00124 * | P(L) | P := hd_prev: Pointer to previous node 00125 * | P(H) | N := hd_next: Pointer to next node 00126 * | N(L) | 00127 * | N(H) | 00128 * +---------------+ 00129 * | unused space | 00130 * ... ... 00131 * | end chunk | 00132 * +---------------+ 00133 * @endverbatim 00134 * 00135 * On an 8-bit MCU with 16-bit addresses, the theoretical minimum size of the 00136 * heap descriptor is 6 bytes. The effective size (due to pointer alignment) 00137 * is usually 8 bytes. On an MCU with 32-bit addresses, the heap descriptor's 00138 * size is 12 bytes. 00139 */ 00140 typedef struct PmHeapDesc_s 00141 { 00142 /** Heap descriptor */ 00143 uint16_t hd; 00144 00145 /** Ptr to prev heap chunk */ 00146 struct PmHeapDesc_s *prev; 00147 00148 /** Ptr to next heap chunk */ 00149 struct PmHeapDesc_s *next; 00150 } PmHeapDesc_t, 00151 *pPmHeapDesc_t; 00152 00153 typedef struct PmHeap_s 00154 { 00155 /** Pointer to base of heap. Set at initialization of VM */ 00156 uint8_t *base; 00157 00158 /** Size of the heap. Set at initialization of VM */ 00159 uint32_t size; 00160 00161 /** Ptr to list of free chunks; sorted smallest to largest. */ 00162 pPmHeapDesc_t pfreelist; 00163 00164 /** The amount of heap space available in free list */ 00165 uint32_t avail; 00166 00167 #ifdef HAVE_GC 00168 /** Garbage collection mark value */ 00169 uint8_t gcval; 00170 00171 /** Boolean to indicate if GC should run automatically */ 00172 uint8_t auto_gc; 00173 00174 /* #239: Fix GC when 2+ unlinked allocs occur */ 00175 /** Stack of objects to be held as temporary roots */ 00176 pPmObj_t temp_roots[HEAP_NUM_TEMP_ROOTS]; 00177 00178 uint8_t temp_root_index; 00179 #endif /* HAVE_GC */ 00180 00181 } PmHeap_t, 00182 *pPmHeap_t; 00183 00184 00185 /** The PyMite heap */ 00186 static PmHeap_t pmHeap PM_PLAT_HEAP_ATTR; 00187 00188 00189 #if 0 00190 static void 00191 heap_gcPrintFreelist(void) 00192 { 00193 pPmHeapDesc_t pchunk = pmHeap.pfreelist; 00194 00195 printf("DEBUG: pmHeap.avail = %d\n", pmHeap.avail); 00196 printf("DEBUG: freelist:\n"); 00197 while (pchunk != C_NULL) 00198 { 00199 printf("DEBUG: free chunk (%d bytes) @ 0x%0x\n", 00200 CHUNK_GET_SIZE(pchunk), (int)pchunk); 00201 pchunk = pchunk->next; 00202 } 00203 } 00204 #endif 00205 00206 00207 #if 0 00208 /** DEBUG: dumps the heap and roots list to a file */ 00209 static void 00210 heap_dump(void) 00211 { 00212 static int n = 0; 00213 uint16_t s; 00214 uint32_t i; 00215 char filename[17] = "pmheapdump0N.bin\0"; 00216 FILE *fp; 00217 00218 filename[11] = '0' + n++; 00219 fp = fopen(filename, "wb"); 00220 00221 /* magic : PMDUMP for little endian or PMUDMP for big endian */ 00222 fwrite(&"PM", 1, 2, fp); 00223 s = 0x5544; 00224 fwrite(&s, sizeof(uint16_t), 1, fp); 00225 fwrite(&"MP", 1, 2, fp); 00226 00227 /* pointer size */ 00228 s = sizeof(intptr_t); 00229 fwrite(&s, sizeof(uint16_t), 1, fp); 00230 00231 /* dump version */ 00232 s = 1; 00233 fwrite(&s, sizeof(uint16_t), 1, fp); 00234 00235 /* pmfeatures */ 00236 s = 0; 00237 #ifdef USE_STRING_CACHE 00238 s |= 1<<0; 00239 #endif 00240 #ifdef HAVE_DEFAULTARGS 00241 s |= 1<<1; 00242 #endif 00243 #ifdef HAVE_CLOSURES 00244 s |= 1<<2; 00245 #endif 00246 #ifdef HAVE_CLASSES 00247 s |= 1<<3; 00248 #endif 00249 fwrite(&s, sizeof(uint16_t), 1, fp); 00250 00251 /* Size of heap */ 00252 fwrite(&pmHeap.size, sizeof(uint32_t), 1, fp); 00253 00254 /* Write base address of heap */ 00255 fwrite((void*)&pmHeap.base, sizeof(intptr_t), 1, fp); 00256 00257 /* Write contents of heap */ 00258 fwrite(pmHeap.base, 1, pmHeap.size, fp); 00259 00260 /* Write num roots*/ 00261 i = 10; 00262 fwrite(&i, sizeof(uint32_t), 1, fp); 00263 00264 /* Write heap root ptrs */ 00265 fwrite((void *)&gVmGlobal.pnone, sizeof(intptr_t), 1, fp); 00266 fwrite((void *)&gVmGlobal.pfalse, sizeof(intptr_t), 1, fp); 00267 fwrite((void *)&gVmGlobal.ptrue, sizeof(intptr_t), 1, fp); 00268 fwrite((void *)&gVmGlobal.pzero, sizeof(intptr_t), 1, fp); 00269 fwrite((void *)&gVmGlobal.pone, sizeof(intptr_t), 1, fp); 00270 fwrite((void *)&gVmGlobal.pnegone, sizeof(intptr_t), 1, fp); 00271 fwrite((void *)&gVmGlobal.pcodeStr, sizeof(intptr_t), 1, fp); 00272 fwrite((void *)&gVmGlobal.builtins, sizeof(intptr_t), 1, fp); 00273 fwrite((void *)&gVmGlobal.nativeframe, sizeof(intptr_t), 1, fp); 00274 fwrite((void *)&gVmGlobal.threadList, sizeof(intptr_t), 1, fp); 00275 fclose(fp); 00276 } 00277 #endif 00278 00279 00280 /* Removes the given chunk from the free list; leaves list in sorted order */ 00281 static PmReturn_t 00282 heap_unlinkFromFreelist(pPmHeapDesc_t pchunk) 00283 { 00284 C_ASSERT(pchunk != C_NULL); 00285 00286 pmHeap.avail -= CHUNK_GET_SIZE(pchunk); 00287 00288 if (pchunk->next != C_NULL) 00289 { 00290 pchunk->next->prev = pchunk->prev; 00291 } 00292 00293 /* If pchunk was the first chunk in the free list, update the heap ptr */ 00294 if (pchunk->prev == C_NULL) 00295 { 00296 pmHeap.pfreelist = pchunk->next; 00297 } 00298 else 00299 { 00300 pchunk->prev->next = pchunk->next; 00301 } 00302 00303 return PM_RET_OK; 00304 } 00305 00306 00307 /* Inserts in order a chunk into the free list. Caller adjusts heap state */ 00308 static PmReturn_t 00309 heap_linkToFreelist(pPmHeapDesc_t pchunk) 00310 { 00311 uint16_t size; 00312 pPmHeapDesc_t pscan; 00313 00314 /* Ensure the object is already free */ 00315 C_ASSERT(OBJ_GET_FREE(pchunk) != 0); 00316 00317 pmHeap.avail += CHUNK_GET_SIZE(pchunk); 00318 00319 /* If free list is empty, add to head of list */ 00320 if (pmHeap.pfreelist == C_NULL) 00321 { 00322 pmHeap.pfreelist = pchunk; 00323 pchunk->next = C_NULL; 00324 pchunk->prev = C_NULL; 00325 00326 return PM_RET_OK; 00327 } 00328 00329 /* Scan free list for insertion point */ 00330 pscan = pmHeap.pfreelist; 00331 size = CHUNK_GET_SIZE(pchunk); 00332 while ((CHUNK_GET_SIZE(pscan) < size) && (pscan->next != C_NULL)) 00333 { 00334 pscan = pscan->next; 00335 } 00336 00337 /* 00338 * Insert chunk after the scan chunk (next is NULL). 00339 * This is a slightly rare case where the last chunk in the free list 00340 * is smaller than the chunk being freed. 00341 */ 00342 if (size > CHUNK_GET_SIZE(pscan)) 00343 { 00344 pchunk->next = pscan->next; 00345 pscan->next = pchunk; 00346 pchunk->prev = pscan; 00347 } 00348 00349 /* Insert chunk before the scan chunk */ 00350 else 00351 { 00352 pchunk->next = pscan; 00353 pchunk->prev = pscan->prev; 00354 00355 /* If chunk will be first item in free list */ 00356 if (pscan->prev == C_NULL) 00357 { 00358 pmHeap.pfreelist = pchunk; 00359 } 00360 else 00361 { 00362 pscan->prev->next = pchunk; 00363 } 00364 pscan->prev = pchunk; 00365 } 00366 00367 return PM_RET_OK; 00368 } 00369 00370 00371 PmReturn_t 00372 heap_init(uint8_t *base, uint32_t size) 00373 { 00374 pPmHeapDesc_t pchunk; 00375 uint32_t hs; 00376 uint8_t *adjbase; 00377 00378 /* Round-up Heap base by the size of the platform pointer */ 00379 adjbase = base + ((sizeof(intptr_t) - 1) & ~(sizeof(intptr_t) - 1)); 00380 pmHeap.base = adjbase; 00381 pmHeap.size = size - (adjbase - base); 00382 00383 #if __DEBUG__ 00384 /* Fill the heap with a non-NULL value to bring out any heap bugs. */ 00385 sli_memset(pmHeap.base, 0xAA, pmHeap.size); 00386 #endif 00387 00388 /* Init heap globals */ 00389 pmHeap.pfreelist = C_NULL; 00390 pmHeap.avail = 0; 00391 #ifdef HAVE_GC 00392 pmHeap.gcval = (uint8_t)0; 00393 pmHeap.temp_root_index = (uint8_t)0; 00394 heap_gcSetAuto(C_TRUE); 00395 #endif /* HAVE_GC */ 00396 00397 pchunk = (pPmHeapDesc_t)pmHeap.base; 00398 hs = pmHeap.size; 00399 00400 /* #180 Proactively link memory previously lost/neglected at tail of heap */ 00401 if ((hs % HEAP_MAX_FREE_CHUNK_SIZE) < HEAP_MIN_CHUNK_SIZE) 00402 { 00403 OBJ_SET_FREE(pchunk, 1); 00404 CHUNK_SET_SIZE(pchunk, HEAP_MIN_CHUNK_SIZE); 00405 heap_linkToFreelist(pchunk); 00406 hs -= HEAP_MIN_CHUNK_SIZE; 00407 pchunk = (pPmHeapDesc_t)((uint8_t *)pchunk + HEAP_MIN_CHUNK_SIZE); 00408 } 00409 00410 /* Create as many max-sized chunks as possible in the freelist */ 00411 for (; 00412 hs >= HEAP_MAX_FREE_CHUNK_SIZE; hs -= HEAP_MAX_FREE_CHUNK_SIZE) 00413 { 00414 OBJ_SET_FREE(pchunk, 1); 00415 CHUNK_SET_SIZE(pchunk, HEAP_MAX_FREE_CHUNK_SIZE); 00416 heap_linkToFreelist(pchunk); 00417 pchunk = (pPmHeapDesc_t)((uint8_t *)pchunk + HEAP_MAX_FREE_CHUNK_SIZE); 00418 } 00419 00420 /* Add any leftover memory to the freelist */ 00421 if (hs >= HEAP_MIN_CHUNK_SIZE) 00422 { 00423 /* Round down to a multiple of four */ 00424 hs = hs & ~3; 00425 OBJ_SET_FREE(pchunk, 1); 00426 CHUNK_SET_SIZE(pchunk, hs); 00427 heap_linkToFreelist(pchunk); 00428 } 00429 00430 C_DEBUG_PRINT(VERBOSITY_LOW, "heap_init(), id=%p, s=%u\n", 00431 pmHeap.base, (unsigned int)pmHeap.avail); 00432 00433 #if USE_STRING_CACHE 00434 string_cacheInit(); 00435 #endif 00436 00437 return PM_RET_OK; 00438 } 00439 00440 00441 /** 00442 * Obtains a chunk of memory from the free list 00443 * 00444 * Performs the Best Fit algorithm. 00445 * Iterates through the freelist to see if a chunk of suitable size exists. 00446 * Shaves a chunk to perfect size iff the remainder is greater than 00447 * the minimum chunk size. 00448 * 00449 * @param size Requested chunk size 00450 * @param r_pchunk Return ptr to chunk 00451 * @return Return status 00452 */ 00453 static PmReturn_t 00454 heap_getChunkImpl(uint16_t size, uint8_t **r_pchunk) 00455 { 00456 PmReturn_t retval; 00457 pPmHeapDesc_t pchunk; 00458 pPmHeapDesc_t premainderChunk; 00459 00460 C_ASSERT(r_pchunk != C_NULL); 00461 00462 /* Skip to the first chunk that can hold the requested size */ 00463 pchunk = pmHeap.pfreelist; 00464 while ((pchunk != C_NULL) && (CHUNK_GET_SIZE(pchunk) < size)) 00465 { 00466 pchunk = pchunk->next; 00467 } 00468 00469 /* No chunk of appropriate size was found, raise OutOfMemory exception */ 00470 if (pchunk == C_NULL) 00471 { 00472 *r_pchunk = C_NULL; 00473 PM_RAISE(retval, PM_RET_EX_MEM); 00474 return retval; 00475 } 00476 00477 /* Remove the chunk from the free list */ 00478 retval = heap_unlinkFromFreelist(pchunk); 00479 PM_RETURN_IF_ERROR(retval); 00480 00481 /* Check if a chunk should be carved from what is available */ 00482 if (CHUNK_GET_SIZE(pchunk) - size >= HEAP_MIN_CHUNK_SIZE) 00483 { 00484 /* Create the heap descriptor for the remainder chunk */ 00485 premainderChunk = (pPmHeapDesc_t)((uint8_t *)pchunk + size); 00486 OBJ_SET_FREE(premainderChunk, 1); 00487 CHUNK_SET_SIZE(premainderChunk, CHUNK_GET_SIZE(pchunk) - size); 00488 00489 /* Put the remainder chunk back in the free list */ 00490 retval = heap_linkToFreelist(premainderChunk); 00491 PM_RETURN_IF_ERROR(retval); 00492 00493 /* Convert the chunk from a heap descriptor to an object descriptor */ 00494 OBJ_SET_SIZE(pchunk, 0); 00495 OBJ_SET_FREE(pchunk, 0); 00496 OBJ_SET_SIZE(pchunk, size); 00497 00498 C_DEBUG_PRINT(VERBOSITY_HIGH, 00499 "heap_getChunkImpl()carved, id=%p, s=%d\n", pchunk, 00500 size); 00501 } 00502 else 00503 { 00504 /* Set chunk's type to none (overwrites size field's high byte) */ 00505 OBJ_SET_TYPE((pPmObj_t)pchunk, OBJ_TYPE_NON); 00506 OBJ_SET_FREE(pchunk, 0); 00507 00508 C_DEBUG_PRINT(VERBOSITY_HIGH, 00509 "heap_getChunkImpl()exact, id=%p, s=%d\n", pchunk, 00510 PM_OBJ_GET_SIZE(pchunk)); 00511 } 00512 00513 /* 00514 * Set the chunk's GC mark so it will be collected during the next GC cycle 00515 * if it is not reachable 00516 */ 00517 OBJ_SET_GCVAL(pchunk, pmHeap.gcval); 00518 00519 /* Return the chunk */ 00520 *r_pchunk = (uint8_t *)pchunk; 00521 00522 return retval; 00523 } 00524 00525 00526 /* 00527 * Allocates chunk of memory. 00528 * Filters out invalid sizes. 00529 * Rounds the size up to the next multiple of the platform pointer size. 00530 * Obtains a chunk of at least the desired size. 00531 */ 00532 PmReturn_t 00533 heap_getChunk(uint16_t requestedsize, uint8_t **r_pchunk) 00534 { 00535 PmReturn_t retval; 00536 uint16_t adjustedsize; 00537 00538 /* Ensure size request is valid */ 00539 if (requestedsize > HEAP_MAX_LIVE_CHUNK_SIZE) 00540 { 00541 PM_RAISE(retval, PM_RET_EX_MEM); 00542 return retval; 00543 } 00544 00545 else if (requestedsize < HEAP_MIN_CHUNK_SIZE) 00546 { 00547 requestedsize = HEAP_MIN_CHUNK_SIZE; 00548 } 00549 00550 /* 00551 * Round up the size to a multiple of N bytes, 00552 * where N is 8 for 64-bit platforms and 4 for all else. 00553 * This maintains pointer alignment in the heap (required). 00554 */ 00555 #ifdef PM_PLAT_POINTER_SIZE 00556 #if PM_PLAT_POINTER_SIZE == 8 00557 adjustedsize = ((requestedsize + 7) & ~7); 00558 #else 00559 adjustedsize = ((requestedsize + 3) & ~3); 00560 #endif /* PM_PLAT_POINTER_SIZE */ 00561 #else 00562 adjustedsize = ((requestedsize + 3) & ~3); 00563 #endif /* PM_PLAT_POINTER_SIZE */ 00564 00565 /* Attempt to get a chunk */ 00566 retval = heap_getChunkImpl(adjustedsize, r_pchunk); 00567 00568 #ifdef HAVE_GC 00569 /* Perform GC if out of memory, gc is enabled and not in native session */ 00570 if ((retval == PM_RET_EX_MEM) && (pmHeap.auto_gc == C_TRUE) 00571 && (gVmGlobal.nativeframe.nf_active == C_FALSE)) 00572 { 00573 retval = heap_gcRun(); 00574 PM_RETURN_IF_ERROR(retval); 00575 00576 /* Attempt to get a chunk */ 00577 retval = heap_getChunkImpl(adjustedsize, r_pchunk); 00578 } 00579 #endif /* HAVE_GC */ 00580 00581 /* Ensure that the pointer is N-byte aligned */ 00582 if (retval == PM_RET_OK) 00583 { 00584 #ifdef PM_PLAT_POINTER_SIZE 00585 #if PM_PLAT_POINTER_SIZE == 8 00586 C_ASSERT(((intptr_t)*r_pchunk & 7) == 0); 00587 #else 00588 C_ASSERT(((intptr_t)*r_pchunk & 3) == 0); 00589 #endif /* PM_PLAT_POINTER_SIZE */ 00590 #else 00591 C_ASSERT(((intptr_t)*r_pchunk & 3) == 0); 00592 #endif /* PM_PLAT_POINTER_SIZE */ 00593 } 00594 00595 return retval; 00596 } 00597 00598 00599 /* Releases chunk to the free list */ 00600 PmReturn_t 00601 heap_freeChunk(pPmObj_t ptr) 00602 { 00603 PmReturn_t retval; 00604 00605 C_DEBUG_PRINT(VERBOSITY_HIGH, "heap_freeChunk(), id=%p, s=%d\n", 00606 ptr, PM_OBJ_GET_SIZE(ptr)); 00607 00608 /* Ensure the chunk falls within the heap */ 00609 C_ASSERT(((uint8_t *)ptr >= &pmHeap.base[0]) 00610 && ((uint8_t *)ptr <= &pmHeap.base[pmHeap.size])); 00611 00612 /* Insert the chunk into the freelist */ 00613 OBJ_SET_FREE(ptr, 1); 00614 00615 /* Clear type so that heap descriptor's size's upper byte is zero */ 00616 OBJ_SET_TYPE(ptr, 0); 00617 retval = heap_linkToFreelist((pPmHeapDesc_t)ptr); 00618 PM_RETURN_IF_ERROR(retval); 00619 00620 return retval; 00621 } 00622 00623 00624 uint32_t 00625 heap_getAvail (void) 00626 { 00627 return pmHeap.avail; 00628 } 00629 00630 00631 uint32_t 00632 heap_getSize (void) 00633 { 00634 return pmHeap.size; 00635 } 00636 00637 00638 #ifdef HAVE_GC 00639 /* 00640 * Marks the given object and the objects it references. 00641 * 00642 * @param pobj Any non-free heap object 00643 * @return Return code 00644 */ 00645 static PmReturn_t 00646 heap_gcMarkObj(pPmObj_t pobj) 00647 { 00648 PmReturn_t retval = PM_RET_OK; 00649 int16_t i = 0; 00650 int16_t n; 00651 PmType_t type; 00652 00653 /* Return if ptr is null or object is already marked */ 00654 if (pobj == C_NULL) 00655 { 00656 return retval; 00657 } 00658 if (OBJ_GET_GCVAL(pobj) == pmHeap.gcval) 00659 { 00660 return retval; 00661 } 00662 00663 /* The pointer must be within the heap (native frame is special case) */ 00664 C_ASSERT((((uint8_t *)pobj >= &pmHeap.base[0]) 00665 && ((uint8_t *)pobj <= &pmHeap.base[pmHeap.size])) 00666 || ((uint8_t *)pobj == (uint8_t *)&gVmGlobal.nativeframe)); 00667 00668 /* The object must not already be free */ 00669 C_ASSERT(OBJ_GET_FREE(pobj) == 0); 00670 00671 type = (PmType_t)OBJ_GET_TYPE(pobj); 00672 switch (type) 00673 { 00674 /* Objects with no references to other objects */ 00675 case OBJ_TYPE_NON: 00676 case OBJ_TYPE_INT: 00677 case OBJ_TYPE_FLT: 00678 case OBJ_TYPE_STR: 00679 case OBJ_TYPE_NOB: 00680 case OBJ_TYPE_BOOL: 00681 case OBJ_TYPE_CIO: 00682 OBJ_SET_GCVAL(pobj, pmHeap.gcval); 00683 break; 00684 00685 case OBJ_TYPE_TUP: 00686 i = ((pPmTuple_t)pobj)->length; 00687 00688 /* Mark tuple head */ 00689 OBJ_SET_GCVAL(pobj, pmHeap.gcval); 00690 00691 /* Mark each obj in tuple */ 00692 while (--i >= 0) 00693 { 00694 retval = heap_gcMarkObj(((pPmTuple_t)pobj)->val[i]); 00695 PM_RETURN_IF_ERROR(retval); 00696 } 00697 break; 00698 00699 case OBJ_TYPE_LST: 00700 00701 /* Mark the list */ 00702 OBJ_SET_GCVAL(pobj, pmHeap.gcval); 00703 00704 /* Mark the seglist */ 00705 retval = heap_gcMarkObj((pPmObj_t)((pPmList_t)pobj)->val); 00706 break; 00707 00708 case OBJ_TYPE_DIC: 00709 /* Mark the dict head */ 00710 OBJ_SET_GCVAL(pobj, pmHeap.gcval); 00711 00712 /* Mark the keys seglist */ 00713 retval = heap_gcMarkObj((pPmObj_t)((pPmDict_t)pobj)->d_keys); 00714 PM_RETURN_IF_ERROR(retval); 00715 00716 /* Mark the vals seglist */ 00717 retval = heap_gcMarkObj((pPmObj_t)((pPmDict_t)pobj)->d_vals); 00718 break; 00719 00720 case OBJ_TYPE_COB: 00721 /* Mark the code obj head */ 00722 OBJ_SET_GCVAL(pobj, pmHeap.gcval); 00723 00724 /* Mark the names tuple */ 00725 retval = heap_gcMarkObj((pPmObj_t)((pPmCo_t)pobj)->co_names); 00726 PM_RETURN_IF_ERROR(retval); 00727 00728 /* Mark the consts tuple */ 00729 retval = heap_gcMarkObj((pPmObj_t)((pPmCo_t)pobj)->co_consts); 00730 PM_RETURN_IF_ERROR(retval); 00731 00732 /* #122: Mark the code image if it is in RAM */ 00733 if (((pPmCo_t)pobj)->co_memspace == MEMSPACE_RAM) 00734 { 00735 retval = heap_gcMarkObj((pPmObj_t) 00736 (((pPmCo_t)pobj)->co_codeimgaddr)); 00737 PM_RETURN_IF_ERROR(retval); 00738 } 00739 00740 #ifdef HAVE_CLOSURES 00741 /* #256: Add support for closures */ 00742 /* Mark the cellvars tuple */ 00743 retval = heap_gcMarkObj((pPmObj_t)((pPmCo_t)pobj)->co_cellvars); 00744 #endif /* HAVE_CLOSURES */ 00745 break; 00746 00747 case OBJ_TYPE_MOD: 00748 case OBJ_TYPE_FXN: 00749 /* Module and Func objs are implemented via the PmFunc_t */ 00750 /* Mark the func obj head */ 00751 OBJ_SET_GCVAL(pobj, pmHeap.gcval); 00752 00753 /* Mark the code obj */ 00754 retval = heap_gcMarkObj((pPmObj_t)((pPmFunc_t)pobj)->f_co); 00755 PM_RETURN_IF_ERROR(retval); 00756 00757 /* Mark the attr dict */ 00758 retval = heap_gcMarkObj((pPmObj_t)((pPmFunc_t)pobj)->f_attrs); 00759 PM_RETURN_IF_ERROR(retval); 00760 00761 /* Mark the globals dict */ 00762 retval = heap_gcMarkObj((pPmObj_t)((pPmFunc_t)pobj)->f_globals); 00763 PM_RETURN_IF_ERROR(retval); 00764 00765 #ifdef HAVE_DEFAULTARGS 00766 /* Mark the default args tuple */ 00767 retval = heap_gcMarkObj((pPmObj_t)((pPmFunc_t)pobj)->f_defaultargs); 00768 PM_RETURN_IF_ERROR(retval); 00769 #endif /* HAVE_DEFAULTARGS */ 00770 00771 #ifdef HAVE_CLOSURES 00772 /* #256: Mark the closure tuple */ 00773 retval = heap_gcMarkObj((pPmObj_t)((pPmFunc_t)pobj)->f_closure); 00774 #endif /* HAVE_CLOSURES */ 00775 break; 00776 00777 #ifdef HAVE_CLASSES 00778 case OBJ_TYPE_CLI: 00779 /* Mark the obj head */ 00780 OBJ_SET_GCVAL(pobj, pmHeap.gcval); 00781 00782 /* Mark the class */ 00783 retval = heap_gcMarkObj((pPmObj_t)((pPmInstance_t)pobj)->cli_class); 00784 PM_RETURN_IF_ERROR(retval); 00785 00786 /* Mark the attrs dict */ 00787 retval = heap_gcMarkObj((pPmObj_t)((pPmInstance_t)pobj)->cli_attrs); 00788 break; 00789 00790 case OBJ_TYPE_MTH: 00791 /* Mark the obj head */ 00792 OBJ_SET_GCVAL(pobj, pmHeap.gcval); 00793 00794 /* Mark the instance */ 00795 retval = heap_gcMarkObj((pPmObj_t)((pPmMethod_t)pobj)->m_instance); 00796 PM_RETURN_IF_ERROR(retval); 00797 00798 /* Mark the func */ 00799 retval = heap_gcMarkObj((pPmObj_t)((pPmMethod_t)pobj)->m_func); 00800 PM_RETURN_IF_ERROR(retval); 00801 00802 /* Mark the attrs dict */ 00803 retval = heap_gcMarkObj((pPmObj_t)((pPmMethod_t)pobj)->m_attrs); 00804 break; 00805 00806 case OBJ_TYPE_CLO: 00807 /* Mark the obj head */ 00808 OBJ_SET_GCVAL(pobj, pmHeap.gcval); 00809 00810 /* Mark the attrs dict */ 00811 retval = heap_gcMarkObj((pPmObj_t)((pPmClass_t)pobj)->cl_attrs); 00812 PM_RETURN_IF_ERROR(retval); 00813 00814 /* Mark the base tuple */ 00815 retval = heap_gcMarkObj((pPmObj_t)((pPmClass_t)pobj)->cl_bases); 00816 break; 00817 #endif /* HAVE_CLASSES */ 00818 00819 /* 00820 * An obj in ram should not be of these types. 00821 * Images arrive in RAM as string objects (image is array of bytes) 00822 */ 00823 case OBJ_TYPE_CIM: 00824 case OBJ_TYPE_NIM: 00825 PM_RAISE(retval, PM_RET_EX_SYS); 00826 return retval; 00827 00828 case OBJ_TYPE_FRM: 00829 { 00830 pPmObj_t *ppobj2 = C_NULL; 00831 00832 /* Mark the frame obj head */ 00833 OBJ_SET_GCVAL(pobj, pmHeap.gcval); 00834 00835 /* Mark the previous frame, if this isn't a generator's frame */ 00836 /* Issue #129: Fix iterator losing its object */ 00837 if ((((pPmFrame_t)pobj)->fo_func->f_co->co_flags & CO_GENERATOR) == 0) 00838 { 00839 retval = heap_gcMarkObj((pPmObj_t)((pPmFrame_t)pobj)->fo_back); 00840 PM_RETURN_IF_ERROR(retval); 00841 } 00842 00843 /* Mark the fxn obj */ 00844 retval = heap_gcMarkObj((pPmObj_t)((pPmFrame_t)pobj)->fo_func); 00845 PM_RETURN_IF_ERROR(retval); 00846 00847 /* Mark the blockstack */ 00848 retval = heap_gcMarkObj((pPmObj_t) 00849 ((pPmFrame_t)pobj)->fo_blockstack); 00850 PM_RETURN_IF_ERROR(retval); 00851 00852 /* Mark the attrs dict */ 00853 retval = heap_gcMarkObj((pPmObj_t)((pPmFrame_t)pobj)->fo_attrs); 00854 PM_RETURN_IF_ERROR(retval); 00855 00856 /* Mark the globals dict */ 00857 retval = heap_gcMarkObj((pPmObj_t)((pPmFrame_t)pobj)->fo_globals); 00858 PM_RETURN_IF_ERROR(retval); 00859 00860 /* Mark each obj in the locals list and the stack */ 00861 ppobj2 = ((pPmFrame_t)pobj)->fo_locals; 00862 while (ppobj2 < ((pPmFrame_t)pobj)->fo_sp) 00863 { 00864 retval = heap_gcMarkObj(*ppobj2); 00865 PM_RETURN_IF_ERROR(retval); 00866 ppobj2++; 00867 } 00868 break; 00869 } 00870 00871 case OBJ_TYPE_BLK: 00872 /* Mark the block obj head */ 00873 OBJ_SET_GCVAL(pobj, pmHeap.gcval); 00874 00875 /* Mark the next block in the stack */ 00876 retval = heap_gcMarkObj((pPmObj_t)((pPmBlock_t)pobj)->next); 00877 break; 00878 00879 case OBJ_TYPE_SGL: 00880 /* Mark the seglist obj head */ 00881 OBJ_SET_GCVAL(pobj, pmHeap.gcval); 00882 00883 /* Mark the seglist's segments */ 00884 n = ((pSeglist_t)pobj)->sl_length; 00885 pobj = (pPmObj_t)((pSeglist_t)pobj)->sl_rootseg; 00886 for (i = 0; i < n; i++) 00887 { 00888 /* Mark the segment item */ 00889 retval = heap_gcMarkObj(((pSegment_t)pobj)->s_val[i % SEGLIST_OBJS_PER_SEG]); 00890 PM_RETURN_IF_ERROR(retval); 00891 00892 /* Mark the segment obj head */ 00893 if ((i % SEGLIST_OBJS_PER_SEG) == 0) 00894 { 00895 OBJ_SET_GCVAL(pobj, pmHeap.gcval); 00896 } 00897 00898 /* Point to the next segment */ 00899 else 00900 if ((i % SEGLIST_OBJS_PER_SEG) == (SEGLIST_OBJS_PER_SEG - 1)) 00901 { 00902 pobj = (pPmObj_t)((pSegment_t)pobj)->next; 00903 if (pobj == C_NULL) 00904 { 00905 break; 00906 } 00907 } 00908 } 00909 break; 00910 00911 case OBJ_TYPE_SQI: 00912 /* Mark the sequence iterator obj head */ 00913 OBJ_SET_GCVAL(pobj, pmHeap.gcval); 00914 00915 /* Mark the sequence */ 00916 retval = heap_gcMarkObj(((pPmSeqIter_t)pobj)->si_sequence); 00917 break; 00918 00919 case OBJ_TYPE_THR: 00920 /* Mark the thread obj head */ 00921 OBJ_SET_GCVAL(pobj, pmHeap.gcval); 00922 00923 /* Mark the current frame */ 00924 retval = heap_gcMarkObj((pPmObj_t)((pPmThread_t)pobj)->pframe); 00925 break; 00926 00927 case OBJ_TYPE_NFM: 00928 /* 00929 * Mark the obj desc. This doesn't really do much since the 00930 * native frame is declared static (not from the heap), but this 00931 * is here in case that ever changes 00932 */ 00933 OBJ_SET_GCVAL(pobj, pmHeap.gcval); 00934 00935 /* Mark the native frame's remaining fields if active */ 00936 if (gVmGlobal.nativeframe.nf_active) 00937 { 00938 /* Mark the frame stack */ 00939 retval = heap_gcMarkObj((pPmObj_t) 00940 gVmGlobal.nativeframe.nf_back); 00941 PM_RETURN_IF_ERROR(retval); 00942 00943 /* Mark the function object */ 00944 retval = heap_gcMarkObj((pPmObj_t) 00945 gVmGlobal.nativeframe.nf_func); 00946 PM_RETURN_IF_ERROR(retval); 00947 00948 /* Mark the stack object */ 00949 retval = heap_gcMarkObj(gVmGlobal.nativeframe.nf_stack); 00950 PM_RETURN_IF_ERROR(retval); 00951 00952 /* Mark the args to the native func */ 00953 for (i = 0; i < NATIVE_GET_NUM_ARGS(); i++) 00954 { 00955 retval = 00956 heap_gcMarkObj(gVmGlobal.nativeframe.nf_locals[i]); 00957 PM_RETURN_IF_ERROR(retval); 00958 } 00959 } 00960 break; 00961 00962 #ifdef HAVE_BYTEARRAY 00963 case OBJ_TYPE_BYA: 00964 OBJ_SET_GCVAL(pobj, pmHeap.gcval); 00965 00966 retval = heap_gcMarkObj((pPmObj_t)((pPmBytearray_t)pobj)->val); 00967 break; 00968 00969 case OBJ_TYPE_BYS: 00970 OBJ_SET_GCVAL(pobj, pmHeap.gcval); 00971 break; 00972 #endif /* HAVE_BYTEARRAY */ 00973 00974 default: 00975 /* There should be no invalid types */ 00976 PM_RAISE(retval, PM_RET_EX_SYS); 00977 break; 00978 } 00979 return retval; 00980 } 00981 00982 00983 /* 00984 * Marks the root objects so they won't be collected during the sweep phase. 00985 * Recursively marks all objects reachable from the roots. 00986 */ 00987 static PmReturn_t 00988 heap_gcMarkRoots(void) 00989 { 00990 PmReturn_t retval; 00991 uint8_t i; 00992 00993 /* Toggle the GC marking value so it differs from the last run */ 00994 pmHeap.gcval ^= 1; 00995 00996 /* Mark the constant objects */ 00997 retval = heap_gcMarkObj(PM_NONE); 00998 PM_RETURN_IF_ERROR(retval); 00999 retval = heap_gcMarkObj(PM_FALSE); 01000 PM_RETURN_IF_ERROR(retval); 01001 retval = heap_gcMarkObj(PM_TRUE); 01002 PM_RETURN_IF_ERROR(retval); 01003 retval = heap_gcMarkObj(PM_ZERO); 01004 PM_RETURN_IF_ERROR(retval); 01005 retval = heap_gcMarkObj(PM_ONE); 01006 PM_RETURN_IF_ERROR(retval); 01007 retval = heap_gcMarkObj(PM_NEGONE); 01008 PM_RETURN_IF_ERROR(retval); 01009 retval = heap_gcMarkObj(PM_CODE_STR); 01010 PM_RETURN_IF_ERROR(retval); 01011 01012 /* Mark the builtins dict */ 01013 retval = heap_gcMarkObj(PM_PBUILTINS); 01014 PM_RETURN_IF_ERROR(retval); 01015 01016 /* Mark the native frame if it is active */ 01017 retval = heap_gcMarkObj((pPmObj_t)&gVmGlobal.nativeframe); 01018 PM_RETURN_IF_ERROR(retval); 01019 01020 /* Mark the thread list */ 01021 retval = heap_gcMarkObj((pPmObj_t)gVmGlobal.threadList); 01022 PM_RETURN_IF_ERROR(retval); 01023 01024 /* Mark the temporary roots */ 01025 for (i = 0; i < pmHeap.temp_root_index; i++) 01026 { 01027 retval = heap_gcMarkObj(pmHeap.temp_roots[i]); 01028 PM_RETURN_IF_ERROR(retval); 01029 } 01030 01031 return retval; 01032 } 01033 01034 01035 #if USE_STRING_CACHE 01036 /** 01037 * Unlinks free objects from the string cache. 01038 * This function must only be called by the GC after the heap has been marked 01039 * and before the heap has been swept. 01040 * 01041 * This solves the problem where a string object would be collected 01042 * but its chunk was still linked into the free list 01043 * 01044 * @param gcval The current value for chunks marked by the GC 01045 */ 01046 static PmReturn_t 01047 heap_purgeStringCache(uint8_t gcval) 01048 { 01049 PmReturn_t retval; 01050 pPmString_t *ppstrcache; 01051 pPmString_t pstr; 01052 01053 /* Update string cache pointer if the first string objs are not marked */ 01054 retval = string_getCache(&ppstrcache); 01055 if (ppstrcache == C_NULL) 01056 { 01057 return retval; 01058 } 01059 while ((*ppstrcache != C_NULL) && (OBJ_GET_GCVAL(*ppstrcache) != gcval)) 01060 { 01061 *ppstrcache = (*ppstrcache)->next; 01062 } 01063 if (*ppstrcache == C_NULL) 01064 { 01065 return retval; 01066 } 01067 01068 /* Unlink remaining strings that are not marked */ 01069 for (pstr = *ppstrcache; pstr->next != C_NULL;) 01070 { 01071 /* Unlink consecutive non-marked strings */ 01072 while ((pstr->next != C_NULL) && (OBJ_GET_GCVAL(pstr->next) != gcval)) 01073 { 01074 pstr->next = pstr->next->next; 01075 } 01076 01077 /* If not at end of cache, string must be marked, skip it */ 01078 if (pstr->next != C_NULL) 01079 { 01080 pstr = pstr->next; 01081 } 01082 } 01083 01084 return retval; 01085 } 01086 #endif 01087 01088 01089 /* 01090 * Reclaims any object that does not have a current mark. 01091 * Puts it in the free list. Coalesces all contiguous free chunks. 01092 */ 01093 static PmReturn_t 01094 heap_gcSweep(void) 01095 { 01096 PmReturn_t retval; 01097 pPmObj_t pobj; 01098 pPmHeapDesc_t pchunk; 01099 uint16_t totalchunksize; 01100 01101 #if USE_STRING_CACHE 01102 retval = heap_purgeStringCache(pmHeap.gcval); 01103 #endif 01104 01105 /* Start at the base of the heap */ 01106 pobj = (pPmObj_t)pmHeap.base; 01107 while ((uint8_t *)pobj < &pmHeap.base[pmHeap.size]) 01108 { 01109 /* Skip to the next unmarked or free chunk within the heap */ 01110 while (!OBJ_GET_FREE(pobj) 01111 && (OBJ_GET_GCVAL(pobj) == pmHeap.gcval) 01112 && ((uint8_t *)pobj < &pmHeap.base[pmHeap.size])) 01113 { 01114 pobj = (pPmObj_t)((uint8_t *)pobj + PM_OBJ_GET_SIZE(pobj)); 01115 } 01116 01117 /* Stop if reached the end of the heap */ 01118 if ((uint8_t *)pobj >= &pmHeap.base[pmHeap.size]) 01119 { 01120 break; 01121 } 01122 01123 /* Accumulate the sizes of all consecutive unmarked or free chunks */ 01124 totalchunksize = 0; 01125 01126 /* Coalesce all contiguous free chunks */ 01127 pchunk = (pPmHeapDesc_t)pobj; 01128 while (OBJ_GET_FREE(pchunk) 01129 || (!OBJ_GET_FREE(pchunk) 01130 && (OBJ_GET_GCVAL(pchunk) != pmHeap.gcval))) 01131 { 01132 /* 01133 * If the chunk is already free, unlink it because its size 01134 * is about to change 01135 */ 01136 if (OBJ_GET_FREE(pchunk)) 01137 { 01138 if ((totalchunksize + CHUNK_GET_SIZE(pchunk)) 01139 > HEAP_MAX_FREE_CHUNK_SIZE) 01140 { 01141 break; 01142 } 01143 retval = heap_unlinkFromFreelist(pchunk); 01144 PM_RETURN_IF_ERROR(retval); 01145 } 01146 01147 /* Otherwise free and reclaim the unmarked chunk */ 01148 else 01149 { 01150 if ((totalchunksize + PM_OBJ_GET_SIZE(pchunk)) 01151 > HEAP_MAX_FREE_CHUNK_SIZE) 01152 { 01153 break; 01154 } 01155 OBJ_SET_TYPE(pchunk, 0); 01156 OBJ_SET_FREE(pchunk, 1); 01157 } 01158 totalchunksize = totalchunksize + CHUNK_GET_SIZE(pchunk); 01159 01160 C_DEBUG_PRINT(VERBOSITY_HIGH, "heap_gcSweep(), id=%p, s=%d\n", 01161 pchunk, CHUNK_GET_SIZE(pchunk)); 01162 01163 /* Proceed to the next chunk */ 01164 pchunk = (pPmHeapDesc_t) 01165 ((uint8_t *)pchunk + CHUNK_GET_SIZE(pchunk)); 01166 01167 /* Stop if it's past the end of the heap */ 01168 if ((uint8_t *)pchunk >= &pmHeap.base[pmHeap.size]) 01169 { 01170 break; 01171 } 01172 } 01173 01174 /* Set the heap descriptor data */ 01175 OBJ_SET_FREE(pobj, 1); 01176 CHUNK_SET_SIZE(pobj, totalchunksize); 01177 01178 /* Insert chunk into free list */ 01179 retval = heap_linkToFreelist((pPmHeapDesc_t)pobj); 01180 PM_RETURN_IF_ERROR(retval); 01181 01182 /* Continue to the next chunk */ 01183 pobj = (pPmObj_t)pchunk; 01184 } 01185 01186 return PM_RET_OK; 01187 } 01188 01189 01190 /* Runs the mark-sweep garbage collector */ 01191 PmReturn_t 01192 heap_gcRun(void) 01193 { 01194 PmReturn_t retval; 01195 01196 /* #239: Fix GC when 2+ unlinked allocs occur */ 01197 /* This assertion fails when there are too many objects on the temporary 01198 * root stack and a GC occurs; consider increasing PM_HEAP_NUM_TEMP_ROOTS 01199 */ 01200 C_ASSERT(pmHeap.temp_root_index < HEAP_NUM_TEMP_ROOTS); 01201 01202 C_DEBUG_PRINT(VERBOSITY_LOW, "heap_gcRun()\n"); 01203 01204 retval = heap_gcMarkRoots(); 01205 PM_RETURN_IF_ERROR(retval); 01206 01207 /*heap_dump();*/ 01208 retval = heap_gcSweep(); 01209 /*heap_dump();*/ 01210 return retval; 01211 } 01212 01213 01214 /* Enables or disables automatic garbage collection */ 01215 PmReturn_t 01216 heap_gcSetAuto(uint8_t auto_gc) 01217 { 01218 pmHeap.auto_gc = auto_gc; 01219 return PM_RET_OK; 01220 } 01221 01222 void heap_gcPushTempRoot(pPmObj_t pobj, uint8_t *r_objid) 01223 { 01224 if (pmHeap.temp_root_index < HEAP_NUM_TEMP_ROOTS) 01225 { 01226 *r_objid = pmHeap.temp_root_index; 01227 pmHeap.temp_roots[pmHeap.temp_root_index] = pobj; 01228 pmHeap.temp_root_index++; 01229 } 01230 return; 01231 } 01232 01233 01234 void heap_gcPopTempRoot(uint8_t objid) 01235 { 01236 pmHeap.temp_root_index = objid; 01237 } 01238 01239 #else 01240 01241 void heap_gcPushTempRoot(pPmObj_t pobj, uint8_t *r_objid) {} 01242 void heap_gcPopTempRoot(uint8_t objid) {} 01243 01244 #endif /* HAVE_GC */
Generated on Tue Jul 12 2022 23:13:47 by 1.7.2