alloc.c revision 1.3.6.1 1 /* $NetBSD: alloc.c,v 1.3.6.1 1999/12/27 18:27:01 wrstuden Exp $ */
2
3 /*
4 * area-based allocation built on malloc/free
5 */
6
7 #include "sh.h"
8
9 #ifdef TEST_ALLOC
10 # define shellf printf
11 # ifndef DEBUG_ALLOC
12 # define DEBUG_ALLOC
13 # endif /* DEBUG_ALLOC */
14 #endif /* TEST_ALLOC */
15
16 #ifdef MEM_DEBUG
17
18 /*
19 * Special versions of alloc routines if doing mem_debug
20 */
21 Area *
22 _chmem_ainit(ap, file, line)
23 Area *ap;
24 const char *file;
25 int line;
26 {
27 ap->freelist = (struct Block *) _chmem_newpool("ainit", (char *) 0, -1,
28 file, line);
29 if (!ap->freelist)
30 aerror(ap, "ainit failed (ie, newpool)");
31 return ap;
32 }
33
34 /* free all object in Area */
35 void
36 _chmem_afreeall(ap, file, line)
37 Area *ap;
38 const char *file;
39 int line;
40 {
41 _chmem_delpool((Chmem_poolp) ap->freelist, 0, file, line);
42 /* Kind of ugly, but it works */
43 _chmem_ainit(ap, file, line);
44 }
45
46 /* allocate object from Area */
47 void *
48 _chmem_alloc(size, ap, file, line)
49 size_t size;
50 Area *ap;
51 const char *file;
52 int line;
53 {
54 return _chmem_mallocp((Chmem_poolp) ap->freelist, size, file, line);
55 }
56
57 /* change size of object -- like realloc */
58 void *
59 _chmem_aresize(ptr, size, ap, file, line)
60 void *ptr;
61 size_t size;
62 Area *ap;
63 const char *file;
64 int line;
65 {
66 if (!ptr)
67 /* Done as realloc(0, size) is not portable */
68 return _chmem_mallocp((Chmem_poolp) ap->freelist, size,
69 file, line);
70 else
71 return _chmem_reallocp((Chmem_poolp) ap->freelist, ptr, size,
72 file, line);
73 }
74
75 void
76 _chmem_afree(ptr, ap, file, line)
77 void *ptr;
78 Area *ap;
79 const char *file;
80 int line;
81 {
82 return _chmem_freep((Chmem_poolp) ap->freelist, ptr, file, line);
83 }
84
85 #else /* MEM_DEBUG */
86
87 # if DEBUG_ALLOC
88 void acheck ARGS((Area *ap));
89 # define ACHECK(ap) acheck(ap)
90 # else /* DEBUG_ALLOC */
91 # define ACHECK(ap)
92 # endif /* DEBUG_ALLOC */
93
94 #define ICELLS 200 /* number of Cells in small Block */
95
96 typedef union Cell Cell;
97 typedef struct Block Block;
98
99 /*
100 * The Cells in a Block are organized as a set of objects.
101 * Each object (pointed to by dp) begins with the block it is in
102 * (dp-2)->block, then has a size in (dp-1)->size, which is
103 * followed with "size" data Cells. Free objects are
104 * linked together via dp->next.
105 */
106
107 #define NOBJECT_FIELDS 2 /* the block and size `fields' */
108
109 union Cell {
110 size_t size;
111 Cell *next;
112 Block *block;
113 struct {int _;} junk; /* alignment */
114 double djunk; /* alignment */
115 };
116
117 struct Block {
118 Block *next; /* list of Blocks in Area */
119 Block *prev; /* previous block in list */
120 Cell *freelist; /* object free list */
121 Cell *last; /* &b.cell[size] */
122 Cell cell [1]; /* [size] Cells for allocation */
123 };
124
125 static Block aempty = {&aempty, &aempty, aempty.cell, aempty.cell};
126
127 static void ablockfree ARGS((Block *bp, Area *ap));
128 static void *asplit ARGS((Area *ap, Block *bp, Cell *fp, Cell *fpp, int cells));
129
130 /* create empty Area */
131 Area *
132 ainit(ap)
133 register Area *ap;
134 {
135 ap->freelist = &aempty;
136 ACHECK(ap);
137 return ap;
138 }
139
140 /* free all object in Area */
141 void
142 afreeall(ap)
143 register Area *ap;
144 {
145 register Block *bp;
146 register Block *tmp;
147
148 ACHECK(ap);
149 bp = ap->freelist;
150 if (bp != NULL && bp != &aempty) {
151 do {
152 tmp = bp;
153 bp = bp->next;
154 free((void*)tmp);
155 } while (bp != ap->freelist);
156 ap->freelist = &aempty;
157 }
158 ACHECK(ap);
159 }
160
161 /* allocate object from Area */
162 void *
163 alloc(size, ap)
164 size_t size;
165 register Area *ap;
166 {
167 int cells, acells;
168 Block *bp = 0;
169 Cell *fp = 0, *fpp = 0;
170
171 ACHECK(ap);
172 if (size <= 0)
173 aerror(ap, "allocate bad size");
174 cells = (unsigned)(size + sizeof(Cell) - 1) / sizeof(Cell);
175
176 /* allocate at least this many cells */
177 acells = cells + NOBJECT_FIELDS;
178
179 /*
180 * Only attempt to track small objects - let malloc deal
181 * with larger objects. (this way we don't have to deal with
182 * coalescing memory, or with releasing it to the system)
183 */
184 if (cells <= ICELLS) {
185 /* find free Cell large enough */
186 for (bp = ap->freelist; ; bp = bp->next) {
187 for (fpp = NULL, fp = bp->freelist;
188 fp != bp->last; fpp = fp, fp = fp->next)
189 {
190 if ((fp-1)->size >= cells)
191 goto Found;
192 }
193 /* wrapped around Block list, create new Block */
194 if (bp->next == ap->freelist) {
195 bp = 0;
196 break;
197 }
198 }
199 /* Not much free space left? Allocate a big object this time */
200 acells += ICELLS;
201 }
202 if (bp == 0) {
203 bp = (Block*) malloc(offsetof(Block, cell[acells]));
204 if (bp == NULL)
205 aerror(ap, "cannot allocate");
206 if (ap->freelist == &aempty) {
207 ap->freelist = bp->next = bp->prev = bp;
208 } else {
209 bp->next = ap->freelist->next;
210 ap->freelist->next->prev = bp;
211 ap->freelist->next = bp;
212 bp->prev = ap->freelist;
213 }
214 bp->last = bp->cell + acells;
215 /* initial free list */
216 fp = bp->freelist = bp->cell + NOBJECT_FIELDS;
217 (fp-1)->size = acells - NOBJECT_FIELDS;
218 (fp-2)->block = bp;
219 fp->next = bp->last;
220 fpp = NULL;
221 }
222
223 Found:
224 return asplit(ap, bp, fp, fpp, cells);
225 }
226
227 /* Do the work of splitting an object into allocated and (possibly) unallocated
228 * objects. Returns the `allocated' object.
229 */
230 static void *
231 asplit(ap, bp, fp, fpp, cells)
232 Area *ap;
233 Block *bp;
234 Cell *fp;
235 Cell *fpp;
236 int cells;
237 {
238 Cell *dp = fp; /* allocated object */
239 int split = (fp-1)->size - cells;
240
241 ACHECK(ap);
242 if (split < 0)
243 aerror(ap, "allocated object too small");
244 if (split <= NOBJECT_FIELDS) { /* allocate all */
245 fp = fp->next;
246 } else { /* allocate head, free tail */
247 Cell *next = fp->next; /* needed, as cells may be 0 */
248 ap->freelist = bp; /* next time, start looking for space here */
249 (fp-1)->size = cells;
250 fp += cells + NOBJECT_FIELDS;
251 (fp-1)->size = split - NOBJECT_FIELDS;
252 (fp-2)->block = bp;
253 fp->next = next;
254 }
255 if (fpp == NULL)
256 bp->freelist = fp;
257 else
258 fpp->next = fp;
259 ACHECK(ap);
260 return (void*) dp;
261 }
262
263 /* change size of object -- like realloc */
264 void *
265 aresize(ptr, size, ap)
266 register void *ptr;
267 size_t size;
268 Area *ap;
269 {
270 int cells;
271 Cell *dp = (Cell*) ptr;
272 int oldcells = dp ? (dp-1)->size : 0;
273
274 ACHECK(ap);
275 if (size <= 0)
276 aerror(ap, "allocate bad size");
277 /* New size (in cells) */
278 cells = (unsigned)(size - 1) / sizeof(Cell) + 1;
279
280 /* Is this a large object? If so, let malloc deal with it
281 * directly (unless we are crossing the ICELLS border, in
282 * which case the alloc/free below handles it - this should
283 * cut down on fragmentation, and will also keep the code
284 * working (as it assumes size < ICELLS means it is not
285 * a `large object').
286 */
287 if (oldcells > ICELLS && cells > ICELLS) {
288 Block *bp = (dp-2)->block;
289 Block *nbp;
290 /* Saved in case realloc fails.. */
291 Block *next = bp->next, *prev = bp->prev;
292
293 if (bp->freelist != bp->last)
294 aerror(ap, "allocation resizing free pointer");
295 nbp = realloc((void *) bp,
296 offsetof(Block, cell[cells + NOBJECT_FIELDS]));
297 if (!nbp) {
298 /* Have to clean up... */
299 /* NOTE: If this code changes, similar changes may be
300 * needed in ablockfree().
301 */
302 if (next == bp) /* only block */
303 ap->freelist = &aempty;
304 else {
305 next->prev = prev;
306 prev->next = next;
307 if (ap->freelist == bp)
308 ap->freelist = next;
309 }
310 aerror(ap, "cannot re-allocate");
311 }
312 /* If location changed, keep pointers straight... */
313 if (nbp != bp) {
314 if (next == bp) /* only one block */
315 nbp->next = nbp->prev = nbp;
316 else {
317 next->prev = nbp;
318 prev->next = nbp;
319 }
320 if (ap->freelist == bp)
321 ap->freelist = nbp;
322 dp = nbp->cell + NOBJECT_FIELDS;
323 (dp-2)->block = nbp;
324 }
325 (dp-1)->size = cells;
326 nbp->last = nbp->cell + cells + NOBJECT_FIELDS;
327 nbp->freelist = nbp->last;
328
329 ACHECK(ap);
330 return (void*) dp;
331 }
332
333 /* Check if we can just grow this cell
334 * (need to check that cells < ICELLS so we don't make an
335 * object a `large' - that would mess everything up).
336 */
337 if (dp && cells > oldcells && cells <= ICELLS) {
338 Cell *fp, *fpp;
339 Block *bp = (dp-2)->block;
340 int need = cells - oldcells - NOBJECT_FIELDS;
341
342 /* XXX if we had a flag in an object indicating
343 * if the object was free/allocated, we could
344 * avoid this loop (perhaps)
345 */
346 for (fpp = NULL, fp = bp->freelist;
347 fp != bp->last
348 && dp + oldcells + NOBJECT_FIELDS <= fp
349 ; fpp = fp, fp = fp->next)
350 {
351 if (dp + oldcells + NOBJECT_FIELDS == fp
352 && (fp-1)->size >= need)
353 {
354 Cell *np = asplit(ap, bp, fp, fpp, need);
355 /* May get more than we need here */
356 (dp-1)->size += (np-1)->size + NOBJECT_FIELDS;
357 ACHECK(ap);
358 return ptr;
359 }
360 }
361 }
362
363 /* Check if we can just shrink this cell
364 * (if oldcells > ICELLS, this is a large object and we leave
365 * it to malloc...)
366 * Note: this also handles cells == oldcells (a no-op).
367 */
368 if (dp && cells <= oldcells && oldcells <= ICELLS) {
369 int split;
370
371 split = oldcells - cells;
372 if (split <= NOBJECT_FIELDS) /* cannot split */
373 ;
374 else { /* shrink head, free tail */
375 Block *bp = (dp-2)->block;
376
377 (dp-1)->size = cells;
378 dp += cells + NOBJECT_FIELDS;
379 (dp-1)->size = split - NOBJECT_FIELDS;
380 (dp-2)->block = bp;
381 afree((void*)dp, ap);
382 }
383 /* ACHECK() done in afree() */
384 return ptr;
385 }
386
387 /* Have to do it the hard way... */
388 ptr = alloc(size, ap);
389 if (dp != NULL) {
390 size_t s = (dp-1)->size * sizeof(Cell);
391 if (s > size)
392 s = size;
393 memcpy(ptr, dp, s);
394 afree((void *) dp, ap);
395 }
396 /* ACHECK() done in alloc()/afree() */
397 return ptr;
398 }
399
400 void
401 afree(ptr, ap)
402 void *ptr;
403 register Area *ap;
404 {
405 register Block *bp;
406 register Cell *fp, *fpp;
407 register Cell *dp = (Cell*)ptr;
408
409 ACHECK(ap);
410 if (ptr == 0)
411 aerror(ap, "freeing null pointer");
412 bp = (dp-2)->block;
413
414 /* If this is a large object, just free it up... */
415 /* Release object... */
416 if ((dp-1)->size > ICELLS) {
417 ablockfree(bp, ap);
418 ACHECK(ap);
419 return;
420 }
421
422 if (dp < &bp->cell[NOBJECT_FIELDS] || dp >= bp->last)
423 aerror(ap, "freeing memory outside of block (corrupted?)");
424
425 /* find position in free list */
426 /* XXX if we had prev/next pointers for objects, this loop could go */
427 for (fpp = NULL, fp = bp->freelist; fp < dp; fpp = fp, fp = fp->next)
428 ;
429
430 if (fp == dp)
431 aerror(ap, "freeing free object");
432
433 /* join object with next */
434 if (dp + (dp-1)->size == fp-NOBJECT_FIELDS) { /* adjacent */
435 (dp-1)->size += (fp-1)->size + NOBJECT_FIELDS;
436 dp->next = fp->next;
437 } else /* non-adjacent */
438 dp->next = fp;
439
440 /* join previous with object */
441 if (fpp == NULL)
442 bp->freelist = dp;
443 else if (fpp + (fpp-1)->size == dp-NOBJECT_FIELDS) { /* adjacent */
444 (fpp-1)->size += (dp-1)->size + NOBJECT_FIELDS;
445 fpp->next = dp->next;
446 } else /* non-adjacent */
447 fpp->next = dp;
448
449 /* If whole block is free (and we have some other blocks
450 * around), release this block back to the system...
451 */
452 if (bp->next != bp && bp->freelist == bp->cell + NOBJECT_FIELDS
453 && bp->freelist + (bp->freelist-1)->size == bp->last
454 /* XXX and the other block has some free memory? */
455 )
456 ablockfree(bp, ap);
457 ACHECK(ap);
458 }
459
460 static void
461 ablockfree(bp, ap)
462 Block *bp;
463 Area *ap;
464 {
465 /* NOTE: If this code changes, similar changes may be
466 * needed in alloc() (where realloc fails).
467 */
468
469 if (bp->next == bp) /* only block */
470 ap->freelist = &aempty;
471 else {
472 bp->next->prev = bp->prev;
473 bp->prev->next = bp->next;
474 if (ap->freelist == bp)
475 ap->freelist = bp->next;
476 }
477 free((void*) bp);
478 }
479
480 # if DEBUG_ALLOC
481 void
482 acheck(ap)
483 Area *ap;
484 {
485 Block *bp, *bpp;
486 Cell *dp, *dptmp, *fp;
487 int ok = 1;
488 int isfree;
489 static int disabled;
490
491 if (disabled)
492 return;
493
494 if (!ap) {
495 disabled = 1;
496 aerror(ap, "acheck: null area pointer");
497 }
498
499 bp = ap->freelist;
500 if (!bp) {
501 disabled = 1;
502 aerror(ap, "acheck: null area freelist");
503 }
504
505 /* Nothing to check... */
506 if (bp == &aempty)
507 return;
508
509 bpp = ap->freelist->prev;
510 while (1) {
511 if (bp->prev != bpp) {
512 shellf("acheck: bp->prev != previous\n");
513 ok = 0;
514 }
515 fp = bp->freelist;
516 for (dp = &bp->cell[NOBJECT_FIELDS]; dp != bp->last; ) {
517 if ((dp-2)->block != bp) {
518 shellf("acheck: fragment's block is wrong\n");
519 ok = 0;
520 }
521 isfree = dp == fp;
522 if ((dp-1)->size == 0 && isfree) {
523 shellf("acheck: 0 size frag\n");
524 ok = 0;
525 }
526 if ((dp-1)->size > ICELLS
527 && !isfree
528 && (dp != &bp->cell[NOBJECT_FIELDS]
529 || dp + (dp-1)->size != bp->last))
530 {
531 shellf("acheck: big cell doesn't make up whole block\n");
532 ok = 0;
533 }
534 if (isfree) {
535 if (dp->next <= dp) {
536 shellf("acheck: free fragment's next <= self\n");
537 ok = 0;
538 }
539 if (dp->next > bp->last) {
540 shellf("acheck: free fragment's next > last\n");
541 ok = 0;
542 }
543 fp = dp->next;
544 }
545 dptmp = dp + (dp-1)->size;
546 if (dptmp > bp->last) {
547 shellf("acheck: next frag out of range\n");
548 ok = 0;
549 break;
550 } else if (dptmp != bp->last) {
551 dptmp += NOBJECT_FIELDS;
552 if (dptmp > bp->last) {
553 shellf("acheck: next frag just out of range\n");
554 ok = 0;
555 break;
556 }
557 }
558 if (isfree && dptmp == fp && dptmp != bp->last) {
559 shellf("acheck: adjacent free frags\n");
560 ok = 0;
561 } else if (dptmp > fp) {
562 shellf("acheck: free frag list messed up\n");
563 ok = 0;
564 }
565 dp = dptmp;
566 }
567 bpp = bp;
568 bp = bp->next;
569 if (bp == ap->freelist)
570 break;
571 }
572 if (!ok) {
573 disabled = 1;
574 aerror(ap, "acheck failed");
575 }
576 }
577
578 void
579 aprint(ap, ptr, size)
580 register Area *ap;
581 void *ptr;
582 size_t size;
583 {
584 Block *bp;
585
586 if (!ap)
587 shellf("aprint: null area pointer\n");
588 else if (!(bp = ap->freelist))
589 shellf("aprint: null area freelist\n");
590 else if (bp == &aempty)
591 shellf("aprint: area is empty\n");
592 else {
593 int i;
594 Cell *dp, *fp;
595 Block *bpp;
596
597 bpp = ap->freelist->prev;
598 for (i = 0; ; i++) {
599 if (ptr) {
600 void *eptr = (void *) (((char *) ptr) + size);
601 /* print block only if it overlaps ptr/size */
602 if (!((ptr >= (void *) bp
603 && ptr <= (void *) bp->last)
604 || (eptr >= (void *) bp
605 && eptr <= (void *) bp->last)))
606 continue;
607 shellf("aprint: overlap of 0x%p .. 0x%p\n",
608 ptr, eptr);
609 }
610 if (bp->prev != bpp || bp->next->prev != bp)
611 shellf(
612 "aprint: BAD prev pointer: bp %p, bp->prev %p, bp->next %p, bpp=%p\n",
613 bp, bp->prev, bp->next, bpp);
614 shellf("aprint: block %2d (p=%p,%p,n=%p): 0x%p .. 0x%p (%ld)\n", i,
615 bp->prev, bp, bp->next,
616 bp->cell, bp->last,
617 (long) ((char *) bp->last - (char *) bp->cell));
618 fp = bp->freelist;
619 if (bp->last <= bp->cell + NOBJECT_FIELDS)
620 shellf(
621 "aprint: BAD bp->last too small: %p <= %p\n",
622 bp->last, bp->cell + NOBJECT_FIELDS);
623 if (bp->freelist < bp->cell + NOBJECT_FIELDS
624 || bp->freelist > bp->last)
625 shellf(
626 "aprint: BAD bp->freelist %p out of range: %p .. %p\n",
627 bp->freelist,
628 bp->cell + NOBJECT_FIELDS, bp->last);
629 for (dp = bp->cell; dp != bp->last ; ) {
630 dp += NOBJECT_FIELDS;
631 shellf(
632 "aprint: 0x%p .. 0x%p (%ld) %s\n",
633 (dp-NOBJECT_FIELDS),
634 (dp-NOBJECT_FIELDS) + (dp-1)->size
635 + NOBJECT_FIELDS,
636 (long) ((dp-1)->size + NOBJECT_FIELDS)
637 * sizeof(Cell),
638 dp == fp ? "free" : "allocated");
639 if ((dp-2)->block != bp)
640 shellf(
641 "aprint: BAD dp->block %p != bp %p\n",
642 (dp-2)->block, bp);
643 if (dp > bp->last)
644 shellf(
645 "aprint: BAD dp gone past block: %p > %p\n",
646 dp, bp->last);
647 if (dp > fp)
648 shellf(
649 "aprint: BAD dp gone past free: %p > %p\n",
650 dp, fp);
651 if (dp == fp) {
652 fp = fp->next;
653 if (fp < dp || fp > bp->last)
654 shellf(
655 "aprint: BAD free object %p out of range: %p .. %p\n",
656 fp,
657 dp, bp->last);
658 }
659 dp += (dp-1)->size;
660 }
661 bpp = bp;
662 bp = bp->next;
663 if (bp == ap->freelist)
664 break;
665 }
666 }
667 }
668 # endif /* DEBUG_ALLOC */
669
670 # ifdef TEST_ALLOC
671
672 Area a;
673 FILE *myout;
674
675 int
676 main(int argc, char **argv)
677 {
678 char buf[1024];
679 struct info {
680 int size;
681 void *value;
682 };
683 struct info info[1024 * 2];
684 int size, ident;
685 int lineno = 0;
686
687 myout = stdout;
688 ainit(&a);
689 while (fgets(buf, sizeof(buf), stdin)) {
690 lineno++;
691 if (buf[0] == '\n' || buf[0] == '#')
692 continue;
693 if (sscanf(buf, " alloc %d = i%d", &size, &ident) == 2) {
694 if (ident < 0 || ident > NELEM(info)) {
695 fprintf(stderr, "bad ident (%d) on line %d\n",
696 ident, lineno);
697 exit(1);
698 }
699 info[ident].value = alloc(info[ident].size = size, &a);
700 printf("%p = alloc(%d) [%d,i%d]\n",
701 info[ident].value, info[ident].size,
702 lineno, ident);
703 memset(info[ident].value, 1, size);
704 continue;
705 }
706 if (sscanf(buf, " afree i%d", &ident) == 1) {
707 if (ident < 0 || ident > NELEM(info)) {
708 fprintf(stderr, "bad ident (%d) on line %d\n",
709 ident, lineno);
710 exit(1);
711 }
712 afree(info[ident].value, &a);
713 printf("afree(%p) [%d,i%d]\n", info[ident].value,
714 lineno, ident);
715 continue;
716 }
717 if (sscanf(buf, " aresize i%d , %d", &ident, &size) == 2) {
718 void *value;
719 if (ident < 0 || ident > NELEM(info)) {
720 fprintf(stderr, "bad ident (%d) on line %d\n",
721 ident, lineno);
722 exit(1);
723 }
724 value = info[ident].value;
725 info[ident].value = aresize(value,
726 info[ident].size = size,
727 &a);
728 printf("%p = aresize(%p, %d) [%d,i%d]\n",
729 info[ident].value, value, info[ident].size,
730 lineno, ident);
731 memset(info[ident].value, 1, size);
732 continue;
733 }
734 if (sscanf(buf, " aprint i%d , %d", &ident, &size) == 2) {
735 if (ident < 0 || ident > NELEM(info)) {
736 fprintf(stderr, "bad ident (%d) on line %d\n",
737 ident, lineno);
738 exit(1);
739 }
740 printf("aprint(%p, %d) [%d,i%d]\n",
741 info[ident].value, size, lineno, ident);
742 aprint(&a, info[ident].value, size);
743 continue;
744 }
745 if (sscanf(buf, " aprint %d", &ident) == 1) {
746 if (ident < 0 || ident > NELEM(info)) {
747 fprintf(stderr, "bad ident (%d) on line %d\n",
748 ident, lineno);
749 exit(1);
750 }
751 printf("aprint(0, 0) [%d]\n", lineno);
752 aprint(&a, 0, 0);
753 continue;
754 }
755 if (sscanf(buf, " afreeall %d", &ident) == 1) {
756 printf("afreeall() [%d]\n", lineno);
757 afreeall(&a);
758 memset(info, 0, sizeof(info));
759 continue;
760 }
761 fprintf(stderr, "unrecognized line (line %d)\n",
762 lineno);
763 exit(1);
764 }
765 return 0;
766 }
767
768 void
769 aerror(Area *ap, const char *msg)
770 {
771 printf("aerror: %s\n", msg);
772 fflush(stdout);
773 abort();
774 }
775
776 # endif /* TEST_ALLOC */
777
778 #endif /* MEM_DEBUG */
779