github.com/v2pro/plz@v0.0.0-20221028024117-e5f9aec5b631/dump/map.go (about)

     1  package dump
     2  
     3  import (
     4  	"context"
     5  	"unsafe"
     6  	"github.com/v2pro/plz/msgfmt/jsonfmt"
     7  	"math"
     8  	"reflect"
     9  	"encoding/json"
    10  )
    11  
    12  // A header for a Go map.
    13  type hmap struct {
    14  	count     int // # live cells == size of map.  Must be first (used by len() builtin)
    15  	flags     uint8
    16  	B         uint8  // log_2 of # of buckets (can hold up to loadFactor * 2^B items)
    17  	noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details
    18  	hash0     uint32 // hash seed
    19  
    20  	buckets    unsafe.Pointer // array of 2^B Buckets. may be nil if count==0.
    21  	oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing
    22  	nevacuate  uintptr        // progress counter for evacuation (buckets less than this have been evacuated)
    23  
    24  	extra *mapextra // optional fields
    25  }
    26  
    27  const bucketCntBits = 3
    28  const bucketCnt = 1 << bucketCntBits
    29  
    30  // A bucket for a Go map.
    31  type bmap struct {
    32  	// tophash generally contains the top byte of the hash value
    33  	// for each key in this bucket. If tophash[0] < minTopHash,
    34  	// tophash[0] is a bucket evacuation state instead.
    35  	tophash [bucketCnt]uint8
    36  	// Followed by bucketCnt keys and then bucketCnt values.
    37  	// NOTE: packing all the keys together and then all the values together makes the
    38  	// code a bit more complicated than alternating key/value/key/value/... but it allows
    39  	// us to eliminate padding which would be needed for, e.g., map[int64]int8.
    40  	// Followed by an overflow pointer.
    41  }
    42  
    43  // mapextra holds fields that are not present on all maps.
    44  type mapextra struct {
    45  	// If both key and value do not contain pointers and are inline, then we mark bucket
    46  	// type as containing no pointers. This avoids scanning such maps.
    47  	// However, bmap.overflow is a pointer. In order to keep overflow buckets
    48  	// alive, we store pointers to all overflow buckets in hmap.overflow and h.map.oldoverflow.
    49  	// overflow and oldoverflow are only used if key and value do not contain pointers.
    50  	// overflow contains overflow buckets for hmap.buckets.
    51  	// oldoverflow contains overflow buckets for hmap.oldbuckets.
    52  	// The indirection allows to store a pointer to the slice in hiter.
    53  	overflow    *[]*bmap
    54  	oldoverflow *[]*bmap
    55  
    56  	// nextOverflow holds a pointer to a free overflow bucket.
    57  	nextOverflow *bmap
    58  }
    59  
    60  var topHashEncoder = jsonfmt.EncoderOf(reflect.ArrayOf(bucketCnt, reflect.TypeOf(uint8(0))))
    61  
    62  type mapEncoder struct {
    63  	bucketSize   uintptr
    64  	keysSize     uintptr
    65  	keysEncoder  jsonfmt.Encoder
    66  	elemsEncoder jsonfmt.Encoder
    67  }
    68  
    69  func newMapEncoder(api jsonfmt.API, valType reflect.Type) *mapEncoder {
    70  	keysEncoder := api.EncoderOf(reflect.ArrayOf(bucketCnt, valType.Key()))
    71  	elemsEncoder := api.EncoderOf(reflect.ArrayOf(bucketCnt, valType.Elem()))
    72  	keysSize := valType.Key().Size() * bucketCnt
    73  	elemsSize := valType.Elem().Size() * bucketCnt
    74  	return &mapEncoder{
    75  		bucketSize:   bucketCnt + keysSize + elemsSize + 8,
    76  		keysSize:     keysSize,
    77  		keysEncoder:  keysEncoder,
    78  		elemsEncoder: elemsEncoder,
    79  	}
    80  }
    81  
    82  func (encoder *mapEncoder) Encode(ctx context.Context, space []byte, ptr unsafe.Pointer) []byte {
    83  	hmap := (*hmap)(ptr)
    84  	space = append(space, `{"count":`...)
    85  	space = jsonfmt.WriteInt64(space, int64(hmap.count))
    86  	space = append(space, `,"flags":`...)
    87  	space = jsonfmt.WriteUint8(space, hmap.flags)
    88  	space = append(space, `,"B":`...)
    89  	space = jsonfmt.WriteUint8(space, hmap.B)
    90  	space = append(space, `,"noverflow":`...)
    91  	space = jsonfmt.WriteUint16(space, hmap.noverflow)
    92  	space = append(space, `,"hash0":`...)
    93  	space = jsonfmt.WriteUint32(space, hmap.hash0)
    94  	space = append(space, `,"buckets":{"__ptr__":"`...)
    95  	bucketsPtr := ptrToStr(uintptr(hmap.buckets))
    96  	space = append(space, bucketsPtr...)
    97  	space = append(space, `"},"oldbuckets":{"__ptr__":"`...)
    98  	oldbucketsPtr := ptrToStr(uintptr(hmap.oldbuckets))
    99  	space = append(space, oldbucketsPtr...)
   100  	space = append(space, `"},"nevacuate":`...)
   101  	space = jsonfmt.WriteUint64(space, uint64(hmap.nevacuate))
   102  	space = append(space, `,"extra":{"__ptr__":"`...)
   103  	extraPtr := ptrToStr(uintptr(unsafe.Pointer(hmap.extra)))
   104  	space = append(space, extraPtr...)
   105  	space = append(space, `"}}`...)
   106  	bucketsCount := int(math.Pow(2, float64(hmap.B)))
   107  	if hmap.buckets != nil {
   108  		bucketsData := encoder.encodeBuckets(ctx, nil, bucketsCount, hmap.buckets)
   109  		addrMap := ctx.Value(addrMapKey).(map[string]json.RawMessage)
   110  		addrMap[bucketsPtr] = json.RawMessage(bucketsData)
   111  	}
   112  	if hmap.oldbuckets != nil {
   113  		oldbucketsData := encoder.encodeBuckets(ctx, nil, bucketsCount / 2, hmap.oldbuckets)
   114  		addrMap := ctx.Value(addrMapKey).(map[string]json.RawMessage)
   115  		addrMap[oldbucketsPtr] = json.RawMessage(oldbucketsData)
   116  	}
   117  	if hmap.extra != nil {
   118  		extraData := encoder.encodeExtra(ctx, nil, unsafe.Pointer(hmap.extra))
   119  		addrMap := ctx.Value(addrMapKey).(map[string]json.RawMessage)
   120  		addrMap[extraPtr] = json.RawMessage(extraData)
   121  	}
   122  	return space
   123  }
   124  
   125  func (encoder *mapEncoder) encodeExtra(ctx context.Context, space []byte, ptr unsafe.Pointer) []byte {
   126  	extra := (*mapextra)(ptr)
   127  	space = append(space, `{"overflow":{"__ptr__":"`...)
   128  	overflowPtr := ptrToStr(uintptr(unsafe.Pointer(extra.overflow)))
   129  	space = append(space, overflowPtr...)
   130  	space = append(space, `"},"oldoverflow":{"__ptr__":"`...)
   131  	oldoverflowPtr := ptrToStr(uintptr(unsafe.Pointer(extra.oldoverflow)))
   132  	space = append(space, oldoverflowPtr...)
   133  	space = append(space, `"},"nextOverflow":{"__ptr__":"`...)
   134  	nextOverflowPtr := ptrToStr(uintptr(unsafe.Pointer(extra.nextOverflow)))
   135  	space = append(space, nextOverflowPtr...)
   136  	space = append(space, `"}}`...)
   137  	if extra.overflow != nil {
   138  		overflowData := encoder.encodeBucketPtrSlice(ctx, nil, unsafe.Pointer(extra.overflow))
   139  		addrMap := ctx.Value(addrMapKey).(map[string]json.RawMessage)
   140  		addrMap[overflowPtr] = overflowData
   141  	}
   142  	if extra.oldoverflow != nil {
   143  		oldoverflowData := encoder.encodeBucketPtrSlice(ctx, nil, unsafe.Pointer(extra.oldoverflow))
   144  		addrMap := ctx.Value(addrMapKey).(map[string]json.RawMessage)
   145  		addrMap[oldoverflowPtr] = oldoverflowData
   146  	}
   147  	if extra.nextOverflow != nil {
   148  		nextOverflowData := encoder.encodeBucket(ctx, nil, unsafe.Pointer(extra.nextOverflow))
   149  		addrMap := ctx.Value(addrMapKey).(map[string]json.RawMessage)
   150  		addrMap[nextOverflowPtr] = nextOverflowData
   151  	}
   152  	return space
   153  }
   154  
   155  // []*bmap
   156  func (encoder *mapEncoder) encodeBucketPtrSlice(ctx context.Context, space []byte, ptr unsafe.Pointer) []byte {
   157  	header := (*sliceHeader)(ptr)
   158  	space = append(space, `{"data":{"__ptr__":"`...)
   159  	ptrStr := ptrToStr(uintptr(header.data))
   160  	space = append(space, ptrStr...)
   161  	space = append(space, `"},"len":`...)
   162  	space = jsonfmt.WriteInt64(space, int64(header.len))
   163  	space = append(space, `,"cap":`...)
   164  	space = jsonfmt.WriteInt64(space, int64(header.cap))
   165  	space = append(space, `}`...)
   166  	data := encoder.encodeBucketPtrArray(ctx, nil, header.len, header.data)
   167  	if uintptr(header.data) != 0 {
   168  		addrMap := ctx.Value(addrMapKey).(map[string]json.RawMessage)
   169  		addrMap[ptrStr] = json.RawMessage(data)
   170  	}
   171  	return space
   172  }
   173  
   174  // [N]*bmap
   175  func (encoder *mapEncoder) encodeBucketPtrArray(ctx context.Context, space []byte, count int, ptr unsafe.Pointer) []byte {
   176  	cursor := uintptr(ptr)
   177  	addrMap := ctx.Value(addrMapKey).(map[string]json.RawMessage)
   178  	space = append(space, '[')
   179  	for i := 0; i < count; i++ {
   180  		if i != 0 {
   181  			space = append(space, ',', ' ')
   182  		}
   183  		elemPtr := *(*unsafe.Pointer)(unsafe.Pointer(cursor + 8 * uintptr(i)))
   184  		space = append(space, `{"__ptr__":"`...)
   185  		elemPtrStr := ptrToStr(uintptr(elemPtr))
   186  		space = append(space, elemPtrStr...)
   187  		space = append(space, `"}`...)
   188  		if elemPtr != nil {
   189  			elemData := encoder.encodeBucket(ctx, nil, elemPtr)
   190  			addrMap[elemPtrStr] = json.RawMessage(elemData)
   191  		}
   192  	}
   193  	space = append(space, ']')
   194  	return space
   195  }
   196  
   197  func (encoder *mapEncoder) encodeBuckets(ctx context.Context, space []byte, count int, ptr unsafe.Pointer) []byte {
   198  	space = append(space, '[')
   199  	cursor := uintptr(ptr)
   200  	for i := 0; i < count; i++ {
   201  		if i != 0 {
   202  			space = append(space, ',', ' ')
   203  		}
   204  		space = encoder.encodeBucket(ctx, space, unsafe.Pointer(cursor))
   205  		cursor += encoder.bucketSize
   206  	}
   207  	space = append(space, ']')
   208  	return space
   209  }
   210  
   211  func (encoder *mapEncoder) encodeBucket(ctx context.Context, space []byte, ptr unsafe.Pointer) []byte {
   212  	bmap := (*bmap)(ptr)
   213  	space = append(space, `{"tophash":`...)
   214  	space = topHashEncoder.Encode(ctx, space, reflect2.PtrOf(bmap.tophash))
   215  	space = append(space, `,"keys":`...)
   216  	keysPtr := uintptr(ptr) + bucketCnt
   217  	space = encoder.keysEncoder.Encode(ctx, space, unsafe.Pointer(keysPtr))
   218  	space = append(space, `,"elems":`...)
   219  	space = encoder.elemsEncoder.Encode(ctx, space, unsafe.Pointer(keysPtr+encoder.keysSize))
   220  	space = append(space, `,"overflow":{"__ptr__":"`...)
   221  	overflowPtr := *(*uintptr)(unsafe.Pointer(uintptr(ptr) + encoder.bucketSize - 8))
   222  	overflowPtrStr := ptrToStr(overflowPtr)
   223  	space = append(space, overflowPtrStr...)
   224  	space = append(space, `"}}`...)
   225  	if overflowPtr != 0 {
   226  		addrMap := ctx.Value(addrMapKey).(map[string]json.RawMessage)
   227  		overflow := encoder.encodeBucket(ctx, nil, unsafe.Pointer(overflowPtr))
   228  		addrMap[overflowPtrStr] = json.RawMessage(overflow)
   229  	}
   230  	return space
   231  }