github.com/llvm-mirror/llgo@v0.0.0-20190322182713-bf6f0a60fce1/third_party/gofrontend/libgo/runtime/heapdump.c (about) 1 // Copyright 2014 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Implementation of runtime/debug.WriteHeapDump. Writes all 6 // objects in the heap plus additional info (roots, threads, 7 // finalizers, etc.) to a file. 8 9 // The format of the dumped file is described at 10 // http://code.google.com/p/go-wiki/wiki/heapdump13 11 12 #include "runtime.h" 13 #include "arch.h" 14 #include "malloc.h" 15 #include "mgc0.h" 16 #include "go-type.h" 17 #include "go-defer.h" 18 #include "go-panic.h" 19 20 #define hash __hash 21 #define KindNoPointers GO_NO_POINTERS 22 23 enum { 24 FieldKindEol = 0, 25 FieldKindPtr = 1, 26 FieldKindString = 2, 27 FieldKindSlice = 3, 28 FieldKindIface = 4, 29 FieldKindEface = 5, 30 31 TagEOF = 0, 32 TagObject = 1, 33 TagOtherRoot = 2, 34 TagType = 3, 35 TagGoRoutine = 4, 36 TagStackFrame = 5, 37 TagParams = 6, 38 TagFinalizer = 7, 39 TagItab = 8, 40 TagOSThread = 9, 41 TagMemStats = 10, 42 TagQueuedFinalizer = 11, 43 TagData = 12, 44 TagBss = 13, 45 TagDefer = 14, 46 TagPanic = 15, 47 TagMemProf = 16, 48 TagAllocSample = 17, 49 50 TypeInfo_Conservative = 127, 51 }; 52 53 // static uintptr* playgcprog(uintptr offset, uintptr *prog, void (*callback)(void*,uintptr,uintptr), void *arg); 54 // static void dumpfields(uintptr *prog); 55 static void dumpefacetypes(void *obj, uintptr size, const Type *type, uintptr kind); 56 57 // fd to write the dump to. 58 static uintptr dumpfd; 59 60 // buffer of pending write data 61 enum { 62 BufSize = 4096, 63 }; 64 static byte buf[BufSize]; 65 static uintptr nbuf; 66 67 static void 68 hwrite(const byte *data, uintptr len) 69 { 70 if(len + nbuf <= BufSize) { 71 runtime_memmove(buf + nbuf, data, len); 72 nbuf += len; 73 return; 74 } 75 runtime_write(dumpfd, buf, nbuf); 76 if(len >= BufSize) { 77 runtime_write(dumpfd, data, len); 78 nbuf = 0; 79 } else { 80 runtime_memmove(buf, data, len); 81 nbuf = len; 82 } 83 } 84 85 static void 86 flush(void) 87 { 88 runtime_write(dumpfd, buf, nbuf); 89 nbuf = 0; 90 } 91 92 // Cache of types that have been serialized already. 93 // We use a type's hash field to pick a bucket. 94 // Inside a bucket, we keep a list of types that 95 // have been serialized so far, most recently used first. 96 // Note: when a bucket overflows we may end up 97 // serializing a type more than once. That's ok. 98 enum { 99 TypeCacheBuckets = 256, // must be a power of 2 100 TypeCacheAssoc = 4, 101 }; 102 typedef struct TypeCacheBucket TypeCacheBucket; 103 struct TypeCacheBucket { 104 const Type *t[TypeCacheAssoc]; 105 }; 106 static TypeCacheBucket typecache[TypeCacheBuckets]; 107 108 // dump a uint64 in a varint format parseable by encoding/binary 109 static void 110 dumpint(uint64 v) 111 { 112 byte buf[10]; 113 int32 n; 114 n = 0; 115 while(v >= 0x80) { 116 buf[n++] = v | 0x80; 117 v >>= 7; 118 } 119 buf[n++] = v; 120 hwrite(buf, n); 121 } 122 123 static void 124 dumpbool(bool b) 125 { 126 dumpint(b ? 1 : 0); 127 } 128 129 // dump varint uint64 length followed by memory contents 130 static void 131 dumpmemrange(const byte *data, uintptr len) 132 { 133 dumpint(len); 134 hwrite(data, len); 135 } 136 137 static void 138 dumpstr(String s) 139 { 140 dumpmemrange(s.str, s.len); 141 } 142 143 static void 144 dumpcstr(const int8 *c) 145 { 146 dumpmemrange((const byte*)c, runtime_findnull((const byte*)c)); 147 } 148 149 // dump information for a type 150 static void 151 dumptype(const Type *t) 152 { 153 TypeCacheBucket *b; 154 int32 i, j; 155 156 if(t == nil) { 157 return; 158 } 159 160 // If we've definitely serialized the type before, 161 // no need to do it again. 162 b = &typecache[t->hash & (TypeCacheBuckets-1)]; 163 if(t == b->t[0]) return; 164 for(i = 1; i < TypeCacheAssoc; i++) { 165 if(t == b->t[i]) { 166 // Move-to-front 167 for(j = i; j > 0; j--) { 168 b->t[j] = b->t[j-1]; 169 } 170 b->t[0] = t; 171 return; 172 } 173 } 174 // Might not have been dumped yet. Dump it and 175 // remember we did so. 176 for(j = TypeCacheAssoc-1; j > 0; j--) { 177 b->t[j] = b->t[j-1]; 178 } 179 b->t[0] = t; 180 181 // dump the type 182 dumpint(TagType); 183 dumpint((uintptr)t); 184 dumpint(t->__size); 185 if(t->__uncommon == nil || t->__uncommon->__pkg_path == nil || t->__uncommon->__name == nil) { 186 dumpstr(*t->__reflection); 187 } else { 188 dumpint(t->__uncommon->__pkg_path->len + 1 + t->__uncommon->__name->len); 189 hwrite(t->__uncommon->__pkg_path->str, t->__uncommon->__pkg_path->len); 190 hwrite((const byte*)".", 1); 191 hwrite(t->__uncommon->__name->str, t->__uncommon->__name->len); 192 } 193 dumpbool(t->__size > PtrSize || (t->__code & KindNoPointers) == 0); 194 // dumpfields((uintptr*)t->gc + 1); 195 } 196 197 // returns true if object is scannable 198 static bool 199 scannable(byte *obj) 200 { 201 uintptr *b, off, shift; 202 203 off = (uintptr*)obj - (uintptr*)runtime_mheap.arena_start; // word offset 204 b = (uintptr*)runtime_mheap.arena_start - off/wordsPerBitmapWord - 1; 205 shift = off % wordsPerBitmapWord; 206 return ((*b >> shift) & bitScan) != 0; 207 } 208 209 // dump an object 210 static void 211 dumpobj(byte *obj, uintptr size, const Type *type, uintptr kind) 212 { 213 if(type != nil) { 214 dumptype(type); 215 dumpefacetypes(obj, size, type, kind); 216 } 217 218 dumpint(TagObject); 219 dumpint((uintptr)obj); 220 dumpint((uintptr)type); 221 dumpint(kind); 222 dumpmemrange(obj, size); 223 } 224 225 static void 226 dumpotherroot(const char *description, byte *to) 227 { 228 dumpint(TagOtherRoot); 229 dumpcstr((const int8 *)description); 230 dumpint((uintptr)to); 231 } 232 233 static void 234 dumpfinalizer(byte *obj, FuncVal *fn, const FuncType* ft, const PtrType *ot) 235 { 236 dumpint(TagFinalizer); 237 dumpint((uintptr)obj); 238 dumpint((uintptr)fn); 239 dumpint((uintptr)fn->fn); 240 dumpint((uintptr)ft); 241 dumpint((uintptr)ot); 242 } 243 244 typedef struct ChildInfo ChildInfo; 245 struct ChildInfo { 246 // Information passed up from the callee frame about 247 // the layout of the outargs region. 248 uintptr argoff; // where the arguments start in the frame 249 uintptr arglen; // size of args region 250 BitVector args; // if args.n >= 0, pointer map of args region 251 252 byte *sp; // callee sp 253 uintptr depth; // depth in call stack (0 == most recent) 254 }; 255 256 static void 257 dumpgoroutine(G *gp) 258 { 259 // ChildInfo child; 260 Defer *d; 261 Panic *p; 262 263 dumpint(TagGoRoutine); 264 dumpint((uintptr)gp); 265 dumpint((uintptr)0); 266 dumpint(gp->goid); 267 dumpint(gp->gopc); 268 dumpint(gp->status); 269 dumpbool(gp->issystem); 270 dumpbool(gp->isbackground); 271 dumpint(gp->waitsince); 272 dumpcstr((const int8 *)gp->waitreason); 273 dumpint((uintptr)0); 274 dumpint((uintptr)gp->m); 275 dumpint((uintptr)gp->defer); 276 dumpint((uintptr)gp->panic); 277 278 // dump stack 279 // child.args.n = -1; 280 // child.arglen = 0; 281 // child.sp = nil; 282 // child.depth = 0; 283 // if(!ScanStackByFrames) 284 // runtime_throw("need frame info to dump stacks"); 285 // runtime_gentraceback(pc, sp, lr, gp, 0, nil, 0x7fffffff, dumpframe, &child, false); 286 287 // dump defer & panic records 288 for(d = gp->defer; d != nil; d = d->__next) { 289 dumpint(TagDefer); 290 dumpint((uintptr)d); 291 dumpint((uintptr)gp); 292 dumpint((uintptr)d->__arg); 293 dumpint((uintptr)d->__frame); 294 dumpint((uintptr)d->__pfn); 295 dumpint((uintptr)0); 296 dumpint((uintptr)d->__next); 297 } 298 for (p = gp->panic; p != nil; p = p->__next) { 299 dumpint(TagPanic); 300 dumpint((uintptr)p); 301 dumpint((uintptr)gp); 302 dumpint((uintptr)p->__arg.__type_descriptor); 303 dumpint((uintptr)p->__arg.__object); 304 dumpint((uintptr)0); 305 dumpint((uintptr)p->__next); 306 } 307 } 308 309 static void 310 dumpgs(void) 311 { 312 G *gp; 313 uint32 i; 314 315 // goroutines & stacks 316 for(i = 0; i < runtime_allglen; i++) { 317 gp = runtime_allg[i]; 318 switch(gp->status){ 319 default: 320 runtime_printf("unexpected G.status %d\n", gp->status); 321 runtime_throw("mark - bad status"); 322 case Gdead: 323 break; 324 case Grunnable: 325 case Gsyscall: 326 case Gwaiting: 327 dumpgoroutine(gp); 328 break; 329 } 330 } 331 } 332 333 static void 334 finq_callback(FuncVal *fn, void *obj, const FuncType *ft, const PtrType *ot) 335 { 336 dumpint(TagQueuedFinalizer); 337 dumpint((uintptr)obj); 338 dumpint((uintptr)fn); 339 dumpint((uintptr)fn->fn); 340 dumpint((uintptr)ft); 341 dumpint((uintptr)ot); 342 } 343 344 345 static void 346 dumproots(void) 347 { 348 MSpan *s, **allspans; 349 uint32 spanidx; 350 Special *sp; 351 SpecialFinalizer *spf; 352 byte *p; 353 354 // data segment 355 // dumpint(TagData); 356 // dumpint((uintptr)data); 357 // dumpmemrange(data, edata - data); 358 // dumpfields((uintptr*)gcdata + 1); 359 360 // bss segment 361 // dumpint(TagBss); 362 // dumpint((uintptr)bss); 363 // dumpmemrange(bss, ebss - bss); 364 // dumpfields((uintptr*)gcbss + 1); 365 366 // MSpan.types 367 allspans = runtime_mheap.allspans; 368 for(spanidx=0; spanidx<runtime_mheap.nspan; spanidx++) { 369 s = allspans[spanidx]; 370 if(s->state == MSpanInUse) { 371 // The garbage collector ignores type pointers stored in MSpan.types: 372 // - Compiler-generated types are stored outside of heap. 373 // - The reflect package has runtime-generated types cached in its data structures. 374 // The garbage collector relies on finding the references via that cache. 375 switch(s->types.compression) { 376 case MTypes_Empty: 377 case MTypes_Single: 378 break; 379 case MTypes_Words: 380 case MTypes_Bytes: 381 dumpotherroot("runtime type info", (byte*)s->types.data); 382 break; 383 } 384 385 // Finalizers 386 for(sp = s->specials; sp != nil; sp = sp->next) { 387 if(sp->kind != KindSpecialFinalizer) 388 continue; 389 spf = (SpecialFinalizer*)sp; 390 p = (byte*)((s->start << PageShift) + spf->special.offset); 391 dumpfinalizer(p, spf->fn, spf->ft, spf->ot); 392 } 393 } 394 } 395 396 // Finalizer queue 397 runtime_iterate_finq(finq_callback); 398 } 399 400 // Bit vector of free marks. 401 // Needs to be as big as the largest number of objects per span. 402 static byte hfree[PageSize/8]; 403 404 static void 405 dumpobjs(void) 406 { 407 uintptr i, j, size, n, off, shift, *bitp, bits, ti, kind; 408 MSpan *s; 409 MLink *l; 410 byte *p; 411 const Type *t; 412 413 for(i = 0; i < runtime_mheap.nspan; i++) { 414 s = runtime_mheap.allspans[i]; 415 if(s->state != MSpanInUse) 416 continue; 417 p = (byte*)(s->start << PageShift); 418 size = s->elemsize; 419 n = (s->npages << PageShift) / size; 420 if(n > PageSize/8) 421 runtime_throw("free array doesn't have enough entries"); 422 for(l = s->freelist; l != nil; l = l->next) { 423 hfree[((byte*)l - p) / size] = true; 424 } 425 for(j = 0; j < n; j++, p += size) { 426 if(hfree[j]) { 427 hfree[j] = false; 428 continue; 429 } 430 off = (uintptr*)p - (uintptr*)runtime_mheap.arena_start; 431 bitp = (uintptr*)runtime_mheap.arena_start - off/wordsPerBitmapWord - 1; 432 shift = off % wordsPerBitmapWord; 433 bits = *bitp >> shift; 434 435 // Skip FlagNoGC allocations (stacks) 436 if((bits & bitAllocated) == 0) 437 continue; 438 439 // extract type and kind 440 ti = runtime_gettype(p); 441 t = (Type*)(ti & ~(uintptr)(PtrSize-1)); 442 kind = ti & (PtrSize-1); 443 444 // dump it 445 if(kind == TypeInfo_Chan) 446 t = ((const ChanType*)t)->__element_type; // use element type for chan encoding 447 if(t == nil && scannable(p)) 448 kind = TypeInfo_Conservative; // special kind for conservatively scanned objects 449 dumpobj(p, size, t, kind); 450 } 451 } 452 } 453 454 static void 455 dumpparams(void) 456 { 457 byte *x; 458 459 dumpint(TagParams); 460 x = (byte*)1; 461 if(*(byte*)&x == 1) 462 dumpbool(false); // little-endian ptrs 463 else 464 dumpbool(true); // big-endian ptrs 465 dumpint(PtrSize); 466 dumpint(runtime_Hchansize); 467 dumpint((uintptr)runtime_mheap.arena_start); 468 dumpint((uintptr)runtime_mheap.arena_used); 469 dumpint(0); 470 dumpcstr((const int8 *)""); 471 dumpint(runtime_ncpu); 472 } 473 474 static void 475 dumpms(void) 476 { 477 M *mp; 478 479 for(mp = runtime_allm; mp != nil; mp = mp->alllink) { 480 dumpint(TagOSThread); 481 dumpint((uintptr)mp); 482 dumpint(mp->id); 483 dumpint(0); 484 } 485 } 486 487 static void 488 dumpmemstats(void) 489 { 490 int32 i; 491 492 dumpint(TagMemStats); 493 dumpint(mstats.alloc); 494 dumpint(mstats.total_alloc); 495 dumpint(mstats.sys); 496 dumpint(mstats.nlookup); 497 dumpint(mstats.nmalloc); 498 dumpint(mstats.nfree); 499 dumpint(mstats.heap_alloc); 500 dumpint(mstats.heap_sys); 501 dumpint(mstats.heap_idle); 502 dumpint(mstats.heap_inuse); 503 dumpint(mstats.heap_released); 504 dumpint(mstats.heap_objects); 505 dumpint(mstats.stacks_inuse); 506 dumpint(mstats.stacks_sys); 507 dumpint(mstats.mspan_inuse); 508 dumpint(mstats.mspan_sys); 509 dumpint(mstats.mcache_inuse); 510 dumpint(mstats.mcache_sys); 511 dumpint(mstats.buckhash_sys); 512 dumpint(mstats.gc_sys); 513 dumpint(mstats.other_sys); 514 dumpint(mstats.next_gc); 515 dumpint(mstats.last_gc); 516 dumpint(mstats.pause_total_ns); 517 for(i = 0; i < 256; i++) 518 dumpint(mstats.pause_ns[i]); 519 dumpint(mstats.numgc); 520 } 521 522 static void 523 dumpmemprof_callback(Bucket *b, uintptr nstk, Location *stk, uintptr size, uintptr allocs, uintptr frees) 524 { 525 uintptr i, pc; 526 byte buf[20]; 527 528 dumpint(TagMemProf); 529 dumpint((uintptr)b); 530 dumpint(size); 531 dumpint(nstk); 532 for(i = 0; i < nstk; i++) { 533 pc = stk[i].pc; 534 if(stk[i].function.len == 0) { 535 runtime_snprintf(buf, sizeof(buf), "%X", (uint64)pc); 536 dumpcstr((int8*)buf); 537 dumpcstr((const int8*)"?"); 538 dumpint(0); 539 } else { 540 dumpstr(stk[i].function); 541 dumpstr(stk[i].filename); 542 dumpint(stk[i].lineno); 543 } 544 } 545 dumpint(allocs); 546 dumpint(frees); 547 } 548 549 static void 550 dumpmemprof(void) 551 { 552 MSpan *s, **allspans; 553 uint32 spanidx; 554 Special *sp; 555 SpecialProfile *spp; 556 byte *p; 557 558 runtime_iterate_memprof(dumpmemprof_callback); 559 560 allspans = runtime_mheap.allspans; 561 for(spanidx=0; spanidx<runtime_mheap.nspan; spanidx++) { 562 s = allspans[spanidx]; 563 if(s->state != MSpanInUse) 564 continue; 565 for(sp = s->specials; sp != nil; sp = sp->next) { 566 if(sp->kind != KindSpecialProfile) 567 continue; 568 spp = (SpecialProfile*)sp; 569 p = (byte*)((s->start << PageShift) + spp->special.offset); 570 dumpint(TagAllocSample); 571 dumpint((uintptr)p); 572 dumpint((uintptr)spp->b); 573 } 574 } 575 } 576 577 static void 578 mdump(G *gp) 579 { 580 const byte *hdr; 581 uintptr i; 582 MSpan *s; 583 584 // make sure we're done sweeping 585 for(i = 0; i < runtime_mheap.nspan; i++) { 586 s = runtime_mheap.allspans[i]; 587 if(s->state == MSpanInUse) 588 runtime_MSpan_EnsureSwept(s); 589 } 590 591 runtime_memclr((byte*)&typecache[0], sizeof(typecache)); 592 hdr = (const byte*)"go1.3 heap dump\n"; 593 hwrite(hdr, runtime_findnull(hdr)); 594 dumpparams(); 595 dumpobjs(); 596 dumpgs(); 597 dumpms(); 598 dumproots(); 599 dumpmemstats(); 600 dumpmemprof(); 601 dumpint(TagEOF); 602 flush(); 603 604 gp->param = nil; 605 gp->status = Grunning; 606 runtime_gogo(gp); 607 } 608 609 void runtime_debug_WriteHeapDump(uintptr) 610 __asm__(GOSYM_PREFIX "runtime_debug.WriteHeapDump"); 611 612 void 613 runtime_debug_WriteHeapDump(uintptr fd) 614 { 615 M *m; 616 G *g; 617 618 // Stop the world. 619 runtime_semacquire(&runtime_worldsema, false); 620 m = runtime_m(); 621 m->gcing = 1; 622 m->locks++; 623 runtime_stoptheworld(); 624 625 // Update stats so we can dump them. 626 // As a side effect, flushes all the MCaches so the MSpan.freelist 627 // lists contain all the free objects. 628 runtime_updatememstats(nil); 629 630 // Set dump file. 631 dumpfd = fd; 632 633 // Call dump routine on M stack. 634 g = runtime_g(); 635 g->status = Gwaiting; 636 g->waitreason = "dumping heap"; 637 runtime_mcall(mdump); 638 639 // Reset dump file. 640 dumpfd = 0; 641 642 // Start up the world again. 643 m->gcing = 0; 644 runtime_semrelease(&runtime_worldsema); 645 runtime_starttheworld(); 646 m->locks--; 647 } 648 649 // Runs the specified gc program. Calls the callback for every 650 // pointer-like field specified by the program and passes to the 651 // callback the kind and offset of that field within the object. 652 // offset is the offset in the object of the start of the program. 653 // Returns a pointer to the opcode that ended the gc program (either 654 // GC_END or GC_ARRAY_NEXT). 655 /* 656 static uintptr* 657 playgcprog(uintptr offset, uintptr *prog, void (*callback)(void*,uintptr,uintptr), void *arg) 658 { 659 uintptr len, elemsize, i, *end; 660 661 for(;;) { 662 switch(prog[0]) { 663 case GC_END: 664 return prog; 665 case GC_PTR: 666 callback(arg, FieldKindPtr, offset + prog[1]); 667 prog += 3; 668 break; 669 case GC_APTR: 670 callback(arg, FieldKindPtr, offset + prog[1]); 671 prog += 2; 672 break; 673 case GC_ARRAY_START: 674 len = prog[2]; 675 elemsize = prog[3]; 676 end = nil; 677 for(i = 0; i < len; i++) { 678 end = playgcprog(offset + prog[1] + i * elemsize, prog + 4, callback, arg); 679 if(end[0] != GC_ARRAY_NEXT) 680 runtime_throw("GC_ARRAY_START did not have matching GC_ARRAY_NEXT"); 681 } 682 prog = end + 1; 683 break; 684 case GC_ARRAY_NEXT: 685 return prog; 686 case GC_CALL: 687 playgcprog(offset + prog[1], (uintptr*)((byte*)prog + *(int32*)&prog[2]), callback, arg); 688 prog += 3; 689 break; 690 case GC_CHAN_PTR: 691 callback(arg, FieldKindPtr, offset + prog[1]); 692 prog += 3; 693 break; 694 case GC_STRING: 695 callback(arg, FieldKindString, offset + prog[1]); 696 prog += 2; 697 break; 698 case GC_EFACE: 699 callback(arg, FieldKindEface, offset + prog[1]); 700 prog += 2; 701 break; 702 case GC_IFACE: 703 callback(arg, FieldKindIface, offset + prog[1]); 704 prog += 2; 705 break; 706 case GC_SLICE: 707 callback(arg, FieldKindSlice, offset + prog[1]); 708 prog += 3; 709 break; 710 case GC_REGION: 711 playgcprog(offset + prog[1], (uintptr*)prog[3] + 1, callback, arg); 712 prog += 4; 713 break; 714 default: 715 runtime_printf("%D\n", (uint64)prog[0]); 716 runtime_throw("bad gc op"); 717 } 718 } 719 } 720 721 static void 722 dump_callback(void *p, uintptr kind, uintptr offset) 723 { 724 USED(&p); 725 dumpint(kind); 726 dumpint(offset); 727 } 728 729 // dumpint() the kind & offset of each field in an object. 730 static void 731 dumpfields(uintptr *prog) 732 { 733 playgcprog(0, prog, dump_callback, nil); 734 dumpint(FieldKindEol); 735 } 736 737 static void 738 dumpeface_callback(void *p, uintptr kind, uintptr offset) 739 { 740 Eface *e; 741 742 if(kind != FieldKindEface) 743 return; 744 e = (Eface*)((byte*)p + offset); 745 dumptype(e->__type_descriptor); 746 } 747 */ 748 749 // The heap dump reader needs to be able to disambiguate 750 // Eface entries. So it needs to know every type that might 751 // appear in such an entry. The following two routines accomplish 752 // that. 753 754 // Dump all the types that appear in the type field of 755 // any Eface contained in obj. 756 static void 757 dumpefacetypes(void *obj __attribute__ ((unused)), uintptr size, const Type *type, uintptr kind) 758 { 759 uintptr i; 760 761 switch(kind) { 762 case TypeInfo_SingleObject: 763 //playgcprog(0, (uintptr*)type->gc + 1, dumpeface_callback, obj); 764 break; 765 case TypeInfo_Array: 766 for(i = 0; i <= size - type->__size; i += type->__size) 767 //playgcprog(i, (uintptr*)type->gc + 1, dumpeface_callback, obj); 768 break; 769 case TypeInfo_Chan: 770 if(type->__size == 0) // channels may have zero-sized objects in them 771 break; 772 for(i = runtime_Hchansize; i <= size - type->__size; i += type->__size) 773 //playgcprog(i, (uintptr*)type->gc + 1, dumpeface_callback, obj); 774 break; 775 } 776 }