Reviewed by John.
[WebKit-https.git] / JavaScriptCore / kjs / collector.cpp
1 // -*- c-basic-offset: 2 -*-
2 /*
3  *  This file is part of the KDE libraries
4  *  Copyright (C) 2003 Apple Computer, Inc.
5  *
6  *  This library is free software; you can redistribute it and/or
7  *  modify it under the terms of the GNU Lesser General Public
8  *  License as published by the Free Software Foundation; either
9  *  version 2 of the License, or (at your option) any later version.
10  *
11  *  This library is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  *  Lesser General Public License for more details.
15  *
16  *  You should have received a copy of the GNU Lesser General Public
17  *  License along with this library; if not, write to the Free Software
18  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  *
20  */
21
22 #include "collector.h"
23
24 #include "value.h"
25 #include "internal.h"
26 #include "list.h"
27
28 #if APPLE_CHANGES
29 #include <CoreFoundation/CoreFoundation.h>
30 #include <cxxabi.h>
31 #include <pthread.h>
32 #include <mach/mach_port.h>
33 #include <mach/task.h>
34 #include <mach/thread_act.h>
35 #endif
36
37 namespace KJS {
38
39 // tunable parameters
40 const int MINIMUM_CELL_SIZE = 56;
41 const int BLOCK_SIZE = (8 * 4096);
42 const int SPARE_EMPTY_BLOCKS = 2;
43 const int MIN_ARRAY_SIZE = 14;
44 const int GROWTH_FACTOR = 2;
45 const int LOW_WATER_FACTOR = 4;
46 const int ALLOCATIONS_PER_COLLECTION = 1000;
47
48 // derived constants
49 const int CELL_ARRAY_LENGTH = (MINIMUM_CELL_SIZE / sizeof(double)) + (MINIMUM_CELL_SIZE % sizeof(double) != 0 ? sizeof(double) : 0);
50 const int CELL_SIZE = CELL_ARRAY_LENGTH * sizeof(double);
51 const int CELLS_PER_BLOCK = ((BLOCK_SIZE * 8 - sizeof(int32_t) * 8 - sizeof(void *) * 8) / (CELL_SIZE * 8));
52
53
54
55 struct CollectorCell {
56   union {
57     double memory[CELL_ARRAY_LENGTH];
58     struct {
59       void *zeroIfFree;
60       CollectorCell *next;
61     } freeCell;
62   } u;
63 };
64
65
66 struct CollectorBlock {
67   CollectorCell cells[CELLS_PER_BLOCK];
68   int32_t usedCells;
69   CollectorCell *freeList;
70 };
71
72 struct CollectorHeap {
73   CollectorBlock **blocks;
74   int numBlocks;
75   int usedBlocks;
76   int firstBlockWithPossibleSpace;
77   
78   CollectorCell **oversizeCells;
79   int numOversizeCells;
80   int usedOversizeCells;
81
82   int numLiveObjects;
83   int numAllocationsSinceLastCollect;
84 };
85
86 static CollectorHeap heap = {NULL, 0, 0, 0, NULL, 0, 0, 0, 0};
87
88 bool Collector::memoryFull = false;
89
90 void* Collector::allocate(size_t s)
91 {
92   assert(Interpreter::lockCount() > 0);
93
94   if (s == 0)
95     return 0L;
96   
97   // collect if needed
98   if (++heap.numAllocationsSinceLastCollect >= ALLOCATIONS_PER_COLLECTION) {
99     collect();
100   }
101   
102   if (s > (unsigned)CELL_SIZE) {
103     // oversize allocator
104     if (heap.usedOversizeCells == heap.numOversizeCells) {
105       heap.numOversizeCells = MAX(MIN_ARRAY_SIZE, heap.numOversizeCells * GROWTH_FACTOR);
106       heap.oversizeCells = (CollectorCell **)realloc(heap.oversizeCells, heap.numOversizeCells * sizeof(CollectorCell *));
107     }
108     
109     void *newCell = malloc(s);
110     heap.oversizeCells[heap.usedOversizeCells] = (CollectorCell *)newCell;
111     heap.usedOversizeCells++;
112     heap.numLiveObjects++;
113
114 #if !USE_CONSERVATIVE_GC
115     ((ValueImp *)(newCell))->_flags = 0;
116 #endif
117     return newCell;
118   }
119   
120   // slab allocator
121   
122   CollectorBlock *targetBlock = NULL;
123   
124   int i;
125   for (i = heap.firstBlockWithPossibleSpace; i < heap.usedBlocks; i++) {
126     if (heap.blocks[i]->usedCells < CELLS_PER_BLOCK) {
127       targetBlock = heap.blocks[i];
128       break;
129     }
130   }
131
132   heap.firstBlockWithPossibleSpace = i;
133   
134   if (targetBlock == NULL) {
135     // didn't find one, need to allocate a new block
136     
137     if (heap.usedBlocks == heap.numBlocks) {
138       heap.numBlocks = MAX(MIN_ARRAY_SIZE, heap.numBlocks * GROWTH_FACTOR);
139       heap.blocks = (CollectorBlock **)realloc(heap.blocks, heap.numBlocks * sizeof(CollectorBlock *));
140     }
141     
142     targetBlock = (CollectorBlock *)calloc(1, sizeof(CollectorBlock));
143     targetBlock->freeList = targetBlock->cells;
144     heap.blocks[heap.usedBlocks] = targetBlock;
145     heap.usedBlocks++;
146   }
147   
148   // find a free spot in the block and detach it from the free list
149   CollectorCell *newCell = targetBlock->freeList;
150
151   if (newCell->u.freeCell.next != NULL) {
152     targetBlock->freeList = newCell->u.freeCell.next;
153   } else if (targetBlock->usedCells == (CELLS_PER_BLOCK - 1)) {
154     // last cell in this block
155     targetBlock->freeList = NULL;
156   } else {
157     // all next pointers are initially 0, meaning "next cell"
158     targetBlock->freeList = newCell + 1;
159   }
160
161   targetBlock->usedCells++;
162   heap.numLiveObjects++;
163
164 #if !USE_CONSERVATIVE_GC
165   ((ValueImp *)(newCell))->_flags = 0;
166 #endif
167   return (void *)(newCell);
168 }
169
170 #if TEST_CONSERVATIVE_GC || USE_CONSERVATIVE_GC
171
172 struct Collector::Thread {
173   Thread(pthread_t pthread, mach_port_t mthread) : posixThread(pthread), machThread(mthread) {}
174   Thread *next;
175   pthread_t posixThread;
176   mach_port_t machThread;
177 };
178
179 pthread_key_t registeredThreadKey;
180 pthread_once_t registeredThreadKeyOnce = PTHREAD_ONCE_INIT;
181 Collector::Thread *registeredThreads;
182   
183 static void destroyRegisteredThread(void *data) 
184 {
185   Collector::Thread *thread = (Collector::Thread *)data;
186
187   if (registeredThreads == thread) {
188     registeredThreads = registeredThreads->next;
189   } else {
190     Collector::Thread *last = registeredThreads;
191     for (Collector::Thread *t = registeredThreads->next; t != NULL; t = t->next) {
192       if (t == thread) {
193         last->next = t->next;
194           break;
195       }
196       last = t;
197     }
198   }
199
200   delete thread;
201 }
202
203 static void initializeRegisteredThreadKey()
204 {
205   pthread_key_create(&registeredThreadKey, destroyRegisteredThread);
206 }
207
208 void Collector::registerThread()
209 {
210   pthread_once(&registeredThreadKeyOnce, initializeRegisteredThreadKey);
211
212   if (!pthread_getspecific(registeredThreadKey)) {
213     pthread_t pthread = pthread_self();
214     Collector::Thread *thread = new Collector::Thread(pthread, pthread_mach_thread_np(pthread));
215     thread->next = registeredThreads;
216     registeredThreads = thread;
217     pthread_setspecific(registeredThreadKey, thread);
218   }
219 }
220
221
222 // cells are 8-byte aligned 
223 #define IS_POINTER_ALIGNED(p) (((int)(p) & 7) == 0)
224
225 void Collector::markStackObjectsConservatively(void *start, void *end)
226 {
227   if (start > end) {
228     void *tmp = start;
229     start = end;
230     end = tmp;
231   }
232
233   assert(((char *)end - (char *)start) < 0x1000000);
234   assert(IS_POINTER_ALIGNED(start));
235   assert(IS_POINTER_ALIGNED(end));
236   
237   char **p = (char **)start;
238   char **e = (char **)end;
239   
240   while (p != e) {
241     char *x = *p++;
242     if (IS_POINTER_ALIGNED(x) && x) {
243       bool good = false;
244       for (int block = 0; block < heap.usedBlocks; block++) {
245         size_t offset = x - (char *)heap.blocks[block];
246         const size_t lastCellOffset = sizeof(CollectorCell) * (CELLS_PER_BLOCK - 1);
247         if (offset <= lastCellOffset && offset % sizeof(CollectorCell) == 0) {
248           good = true;
249           break;
250         }
251       }
252       
253       if (!good) {
254         int n = heap.usedOversizeCells;
255         for (int i = 0; i != n; i++) {
256           if (x == (char *)heap.oversizeCells[i]) {
257             good = true;
258             break;
259           }
260         }
261       }
262       
263       if (good && ((CollectorCell *)x)->u.freeCell.zeroIfFree != 0) {
264         ValueImp *imp = (ValueImp *)x;
265         if (!imp->marked())
266           imp->mark();
267       }
268     }
269   }
270 }
271
272 void Collector::markCurrentThreadConservatively()
273 {
274   jmp_buf registers;
275   setjmp(registers);
276
277   pthread_t thread = pthread_self();
278   void *stackBase = pthread_get_stackaddr_np(thread);
279   int dummy;
280   void *stackPointer = &dummy;
281   markStackObjectsConservatively(stackPointer, stackBase);
282 }
283
284 typedef unsigned long usword_t; // word size, assumed to be either 32 or 64 bit
285
286 void Collector::markOtherThreadConservatively(Thread *thread)
287 {
288   thread_suspend(thread->machThread);
289
290 #if defined(__i386__)
291   i386_thread_state_t regs;
292   unsigned user_count = sizeof(regs)/sizeof(int);
293   thread_state_flavor_t flavor = i386_THREAD_STATE;
294 #elif defined(__ppc__)
295   ppc_thread_state_t  regs;
296   unsigned user_count = PPC_THREAD_STATE_COUNT;
297   thread_state_flavor_t flavor = PPC_THREAD_STATE;
298 #elif defined(__ppc64__)
299   ppc_thread_state64_t  regs;
300   unsigned user_count = PPC_THREAD_STATE64_COUNT;
301   thread_state_flavor_t flavor = PPC_THREAD_STATE64;
302 #else
303 #error Unknown Architecture
304 #endif
305   // get the thread register state
306   thread_get_state(thread->machThread, flavor, (thread_state_t)&regs, &user_count);
307   
308   // scan the registers
309   markStackObjectsConservatively((void *)&regs, (void *)((char *)&regs + (user_count * sizeof(usword_t))));
310   
311   // scan the stack
312 #if defined(__i386__)
313   markStackObjectsConservatively((void *)regs.esp, pthread_get_stackaddr_np(thread->posixThread));
314 #elif defined(__ppc__) || defined(__ppc64__)
315   markStackObjectsConservatively((void *)regs.r1, pthread_get_stackaddr_np(thread->posixThread));
316 #else
317 #error Unknown Architecture
318 #endif
319
320   thread_resume(thread->machThread);
321 }
322
323 void Collector::markStackObjectsConservatively()
324 {
325   markCurrentThreadConservatively();
326
327   for (Thread *thread = registeredThreads; thread != NULL; thread = thread->next) {
328     if (thread->posixThread != pthread_self()) {
329       markOtherThreadConservatively(thread);
330     }
331   }
332 }
333
334 void Collector::markProtectedObjects()
335 {
336   for (int i = 0; i < ProtectedValues::_tableSize; i++) {
337     ValueImp *val = ProtectedValues::_table[i].key;
338     if (val && !val->marked()) {
339       val->mark();
340     }
341   }
342 }
343
344 #endif
345
346 bool Collector::collect()
347 {
348   assert(Interpreter::lockCount() > 0);
349
350   bool deleted = false;
351
352 #if TEST_CONSERVATIVE_GC
353   // CONSERVATIVE MARK: mark the root set using conservative GC bit (will compare later)
354   ValueImp::useConservativeMark(true);
355 #endif
356
357 #if USE_CONSERVATIVE_GC || TEST_CONSERVATIVE_GC
358   if (InterpreterImp::s_hook) {
359     InterpreterImp *scr = InterpreterImp::s_hook;
360     do {
361       //fprintf( stderr, "Collector marking interpreter %p\n",(void*)scr);
362       scr->mark();
363       scr = scr->next;
364     } while (scr != InterpreterImp::s_hook);
365   }
366
367   markStackObjectsConservatively();
368   markProtectedObjects();
369   List::markProtectedLists();
370 #endif
371
372 #if TEST_CONSERVATIVE_GC
373   ValueImp::useConservativeMark(false);
374 #endif
375
376 #if !USE_CONSERVATIVE_GC
377   // MARK: first mark all referenced objects recursively
378   // starting out from the set of root objects
379   if (InterpreterImp::s_hook) {
380     InterpreterImp *scr = InterpreterImp::s_hook;
381     do {
382       //fprintf( stderr, "Collector marking interpreter %p\n",(void*)scr);
383       scr->mark();
384       scr = scr->next;
385     } while (scr != InterpreterImp::s_hook);
386   }
387   
388   // mark any other objects that we wouldn't delete anyway
389   for (int block = 0; block < heap.usedBlocks; block++) {
390
391     int minimumCellsToProcess = heap.blocks[block]->usedCells;
392     CollectorBlock *curBlock = heap.blocks[block];
393
394     for (int cell = 0; cell < CELLS_PER_BLOCK; cell++) {
395       if (minimumCellsToProcess < cell) {
396         goto skip_block_mark;
397       }
398         
399       ValueImp *imp = (ValueImp *)(curBlock->cells + cell);
400
401       if (((CollectorCell *)imp)->u.freeCell.zeroIfFree != 0) {
402         
403         if ((imp->_flags & (ValueImp::VI_CREATED|ValueImp::VI_MARKED)) == ValueImp::VI_CREATED &&
404             ((imp->_flags & ValueImp::VI_GCALLOWED) == 0 || imp->refcount != 0)) {
405           imp->mark();
406         }
407       } else {
408         minimumCellsToProcess++;
409       }
410     }
411   skip_block_mark: ;
412   }
413   
414   for (int cell = 0; cell < heap.usedOversizeCells; cell++) {
415     ValueImp *imp = (ValueImp *)heap.oversizeCells[cell];
416     if ((imp->_flags & (ValueImp::VI_CREATED|ValueImp::VI_MARKED)) == ValueImp::VI_CREATED &&
417         ((imp->_flags & ValueImp::VI_GCALLOWED) == 0 || imp->refcount != 0)) {
418       imp->mark();
419     }
420   }
421 #endif
422
423   // SWEEP: delete everything with a zero refcount (garbage) and unmark everything else
424   
425   int emptyBlocks = 0;
426
427   for (int block = 0; block < heap.usedBlocks; block++) {
428     CollectorBlock *curBlock = heap.blocks[block];
429
430     int minimumCellsToProcess = curBlock->usedCells;
431
432     for (int cell = 0; cell < CELLS_PER_BLOCK; cell++) {
433       if (minimumCellsToProcess < cell) {
434         goto skip_block_sweep;
435       }
436
437       ValueImp *imp = (ValueImp *)(curBlock->cells + cell);
438
439       if (((CollectorCell *)imp)->u.freeCell.zeroIfFree != 0) {
440 #if USE_CONSERVATIVE_GC
441         if (!imp->_marked)
442 #else
443         if (!imp->refcount && imp->_flags == (ValueImp::VI_GCALLOWED | ValueImp::VI_CREATED))
444 #endif
445         {
446           //fprintf( stderr, "Collector::deleting ValueImp %p (%s)\n", (void*)imp, typeid(*imp).name());
447           // emulate destructing part of 'operator delete()'
448           imp->~ValueImp();
449           curBlock->usedCells--;
450           heap.numLiveObjects--;
451           deleted = true;
452
453           // put it on the free list
454           ((CollectorCell *)imp)->u.freeCell.zeroIfFree = 0;
455           ((CollectorCell *)imp)->u.freeCell.next = curBlock->freeList;
456           curBlock->freeList = (CollectorCell *)imp;
457
458         } else {
459 #if USE_CONSERVATIVE_GC
460           imp->_marked = 0;
461 #elif TEST_CONSERVATIVE_GC
462           imp->_flags &= ~(ValueImp::VI_MARKED | ValueImp::VI_CONSERVATIVE_MARKED);
463 #else
464           imp->_flags &= ~ValueImp::VI_MARKED;
465 #endif
466         }
467       } else {
468         minimumCellsToProcess++;
469       }
470     }
471
472   skip_block_sweep:
473
474     if (heap.blocks[block]->usedCells == 0) {
475       emptyBlocks++;
476       if (emptyBlocks > SPARE_EMPTY_BLOCKS) {
477 #if !DEBUG_COLLECTOR
478         free(heap.blocks[block]);
479 #endif
480         // swap with the last block so we compact as we go
481         heap.blocks[block] = heap.blocks[heap.usedBlocks - 1];
482         heap.usedBlocks--;
483         block--; // Don't move forward a step in this case
484
485         if (heap.numBlocks > MIN_ARRAY_SIZE && heap.usedBlocks < heap.numBlocks / LOW_WATER_FACTOR) {
486           heap.numBlocks = heap.numBlocks / GROWTH_FACTOR; 
487           heap.blocks = (CollectorBlock **)realloc(heap.blocks, heap.numBlocks * sizeof(CollectorBlock *));
488         }
489       } 
490     }
491   }
492
493   if (deleted) {
494     heap.firstBlockWithPossibleSpace = 0;
495   }
496   
497   int cell = 0;
498   while (cell < heap.usedOversizeCells) {
499     ValueImp *imp = (ValueImp *)heap.oversizeCells[cell];
500     
501 #if USE_CONSERVATIVE_GC
502     if (!imp->_marked) {
503 #else
504     if (!imp->refcount && 
505         imp->_flags == (ValueImp::VI_GCALLOWED | ValueImp::VI_CREATED)) {
506 #endif
507
508       imp->~ValueImp();
509 #if DEBUG_COLLECTOR
510       heap.oversizeCells[cell]->u.freeCell.zeroIfFree = 0;
511 #else
512       free((void *)imp);
513 #endif
514
515       // swap with the last oversize cell so we compact as we go
516       heap.oversizeCells[cell] = heap.oversizeCells[heap.usedOversizeCells - 1];
517
518       heap.usedOversizeCells--;
519       deleted = true;
520       heap.numLiveObjects--;
521
522       if (heap.numOversizeCells > MIN_ARRAY_SIZE && heap.usedOversizeCells < heap.numOversizeCells / LOW_WATER_FACTOR) {
523         heap.numOversizeCells = heap.numOversizeCells / GROWTH_FACTOR; 
524         heap.oversizeCells = (CollectorCell **)realloc(heap.oversizeCells, heap.numOversizeCells * sizeof(CollectorCell *));
525       }
526
527     } else {
528 #if USE_CONSERVATIVE_GC
529       imp->_marked = 0;
530 #elif TEST_CONSERVATIVE_GC
531       imp->_flags &= ~(ValueImp::VI_MARKED | ValueImp::VI_CONSERVATIVE_MARKED);
532 #else
533       imp->_flags &= ~ValueImp::VI_MARKED;
534 #endif
535       cell++;
536     }
537   }
538   
539   heap.numAllocationsSinceLastCollect = 0;
540   
541   memoryFull = (heap.numLiveObjects >= KJS_MEM_LIMIT);
542
543   return deleted;
544 }
545
546 int Collector::size() 
547 {
548   return heap.numLiveObjects; 
549 }
550
551 #ifdef KJS_DEBUG_MEM
552 void Collector::finalCheck()
553 {
554 }
555 #endif
556
557 #if APPLE_CHANGES
558
559 int Collector::numInterpreters()
560 {
561   int count = 0;
562   if (InterpreterImp::s_hook) {
563     InterpreterImp *scr = InterpreterImp::s_hook;
564     do {
565       ++count;
566       scr = scr->next;
567     } while (scr != InterpreterImp::s_hook);
568   }
569   return count;
570 }
571
572 int Collector::numGCNotAllowedObjects()
573 {
574   int count = 0;
575 #if !USE_CONSERVATIVE_GC
576   for (int block = 0; block < heap.usedBlocks; block++) {
577     CollectorBlock *curBlock = heap.blocks[block];
578
579     for (int cell = 0; cell < CELLS_PER_BLOCK; cell++) {
580       ValueImp *imp = (ValueImp *)(curBlock->cells + cell);
581       
582       if (((CollectorCell *)imp)->u.freeCell.zeroIfFree != 0 &&
583           (imp->_flags & ValueImp::VI_GCALLOWED) == 0) {
584         ++count;
585       }
586     }
587   }
588   
589   for (int cell = 0; cell < heap.usedOversizeCells; cell++) {
590     ValueImp *imp = (ValueImp *)heap.oversizeCells[cell];
591     if ((imp->_flags & ValueImp::VI_GCALLOWED) == 0) {
592       ++count;
593     }
594   }
595 #endif
596
597   return count;
598 }
599
600 int Collector::numReferencedObjects()
601 {
602   int count = 0;
603
604 #if USE_CONSERVATIVE_GC
605   for (int i = 0; i < ProtectedValues::_tableSize; i++) {
606     ValueImp *val = ProtectedValues::_table[i].key;
607     if (val) {
608       ++count;
609     }
610   }
611
612 #else
613
614   for (int block = 0; block < heap.usedBlocks; block++) {
615     CollectorBlock *curBlock = heap.blocks[block];
616
617     for (int cell = 0; cell < CELLS_PER_BLOCK; cell++) {
618       ValueImp *imp = (ValueImp *)(curBlock->cells + cell);
619       
620       if (((CollectorCell *)imp)->u.freeCell.zeroIfFree != 0 &&
621           imp->refcount != 0) {
622         ++count;
623       }
624     }
625   }
626   
627   for (int cell = 0; cell < heap.usedOversizeCells; cell++) {
628     ValueImp *imp = (ValueImp *)heap.oversizeCells[cell];
629       if (imp->refcount != 0) {
630         ++count;
631       }
632   }
633 #endif
634
635   return count;
636 }
637
638 const void *Collector::rootObjectClasses()
639 {
640   CFMutableSetRef classes = CFSetCreateMutable(NULL, 0, &kCFTypeSetCallBacks);
641
642 #if USE_CONSERVATIVE_GC
643   for (int i = 0; i < ProtectedValues::_tableSize; i++) {
644     ValueImp *val = ProtectedValues::_table[i].key;
645     if (val) {
646       const char *mangled_name = typeid(*val).name();
647       int status;
648       char *demangled_name = __cxxabiv1::__cxa_demangle (mangled_name, NULL, NULL, &status);
649       
650       CFStringRef className = CFStringCreateWithCString(NULL, demangled_name, kCFStringEncodingASCII);
651       free(demangled_name);
652       CFSetAddValue(classes, className);
653       CFRelease(className);
654     }
655   }
656 #else
657   for (int block = 0; block < heap.usedBlocks; block++) {
658     CollectorBlock *curBlock = heap.blocks[block];
659     for (int cell = 0; cell < CELLS_PER_BLOCK; cell++) {
660       ValueImp *imp = (ValueImp *)(curBlock->cells + cell);
661       
662       if (((CollectorCell *)imp)->u.freeCell.zeroIfFree != 0 &&
663           ((imp->_flags & ValueImp::VI_GCALLOWED) == 0 || imp->refcount != 0)) {
664         const char *mangled_name = typeid(*imp).name();
665         int status;
666         char *demangled_name = __cxxabiv1::__cxa_demangle (mangled_name, NULL, NULL, &status);
667         
668         CFStringRef className = CFStringCreateWithCString(NULL, demangled_name, kCFStringEncodingASCII);
669         free(demangled_name);
670         CFSetAddValue(classes, className);
671         CFRelease(className);
672       }
673     }
674   }
675   
676   for (int cell = 0; cell < heap.usedOversizeCells; cell++) {
677     ValueImp *imp = (ValueImp *)heap.oversizeCells[cell];
678     
679     if ((imp->_flags & ValueImp::VI_GCALLOWED) == 0 || imp->refcount != 0) {
680       const char *mangled_name = typeid(*imp).name();
681       int status;
682       char *demangled_name = __cxxabiv1::__cxa_demangle (mangled_name, NULL, NULL, &status);
683       
684       CFStringRef className = CFStringCreateWithCString(NULL, demangled_name, kCFStringEncodingASCII);
685       free(demangled_name);
686       CFSetAddValue(classes, className);
687       CFRelease(className);
688     }
689   }
690 #endif
691   
692   return classes;
693 }
694
695 #endif // APPLE_CHANGES
696
697 } // namespace KJS