github.com/songzhibin97/go-baseutils@v0.0.2-0.20240302024150-487d8ce9c082/internal/wyhash/digest.go (about)

     1  package wyhash
     2  
     3  import (
     4  	"reflect"
     5  	"unsafe"
     6  
     7  	"github.com/songzhibin97/go-baseutils/internal/runtimex"
     8  )
     9  
    10  type Digest struct {
    11  	initseed uint64
    12  	seed     uint64
    13  	see1     uint64
    14  	data     [64]byte
    15  	length   int
    16  	total    int
    17  }
    18  
    19  // New creates a new Digest that computes the 64-bit wyhash algorithm.
    20  func New(seed uint64) *Digest {
    21  	return &Digest{
    22  		seed:     seed,
    23  		initseed: seed,
    24  		see1:     seed,
    25  	}
    26  }
    27  
    28  func NewDefault() *Digest {
    29  	return New(DefaultSeed)
    30  }
    31  
    32  // Size always returns 8 bytes.
    33  func (d *Digest) Size() int { return 8 }
    34  
    35  // BlockSize always returns 64 bytes.
    36  func (d *Digest) BlockSize() int { return 64 }
    37  
    38  // Reset the digest, and the seed will be reset to initseed.
    39  // The initseed is the seed when digest has been created.
    40  func (d *Digest) Reset() {
    41  	d.seed = d.initseed
    42  	d.see1 = d.initseed
    43  	d.total = 0
    44  	d.length = 0
    45  }
    46  
    47  func (d *Digest) SetSeed(seed uint64)     { d.seed = seed }
    48  func (d *Digest) Seed() uint64            { return d.seed }
    49  func (d *Digest) SetInitSeed(seed uint64) { d.initseed = seed }
    50  func (d *Digest) InitSeed() uint64        { return d.initseed }
    51  
    52  // Write (via the embedded io.Writer interface) adds more data to the running hash.
    53  // It never returns an error.
    54  func (d *Digest) Write(input []byte) (int, error) {
    55  	ilen := len(input)
    56  	d.total += ilen
    57  
    58  	if d.length+ilen <= 64 {
    59  		copy(d.data[d.length:d.length+ilen], input)
    60  		d.length += ilen
    61  		return ilen, nil
    62  	}
    63  
    64  	// Consume first 64 bytes if possible.
    65  	if d.length != 0 {
    66  		inputpre := 64 - d.length // the data from input to d.data
    67  		copy(d.data[d.length:], input[:inputpre])
    68  		input = input[inputpre:] // free preceding data
    69  
    70  		paddr := unsafe.Pointer(&d.data)
    71  		d.seed = _wymix(runtimex.ReadUnaligned64(paddr)^s1, runtimex.ReadUnaligned64(add(paddr, 8))^d.seed) ^ _wymix(runtimex.ReadUnaligned64(add(paddr, 16))^s2, runtimex.ReadUnaligned64(add(paddr, 24))^d.seed)
    72  		d.see1 = _wymix(runtimex.ReadUnaligned64(add(paddr, 32))^s3, runtimex.ReadUnaligned64(add(paddr, 40))^d.see1) ^ _wymix(runtimex.ReadUnaligned64(add(paddr, 48))^s4, runtimex.ReadUnaligned64(add(paddr, 56))^d.see1)
    73  
    74  		d.length = 0 // free d.data, since it has been consumed
    75  	}
    76  
    77  	// If remain input still greater than 64.
    78  	seed, see1 := d.seed, d.see1
    79  	for len(input) > 64 {
    80  		paddr := *(*unsafe.Pointer)(unsafe.Pointer(&input))
    81  		seed = _wymix(runtimex.ReadUnaligned64(paddr)^s1, runtimex.ReadUnaligned64(add(paddr, 8))^seed) ^ _wymix(runtimex.ReadUnaligned64(add(paddr, 16))^s2, runtimex.ReadUnaligned64(add(paddr, 24))^seed)
    82  		see1 = _wymix(runtimex.ReadUnaligned64(add(paddr, 32))^s3, runtimex.ReadUnaligned64(add(paddr, 40))^see1) ^ _wymix(runtimex.ReadUnaligned64(add(paddr, 48))^s4, runtimex.ReadUnaligned64(add(paddr, 56))^see1)
    83  		input = input[64:]
    84  	}
    85  	d.seed, d.see1 = seed, see1
    86  
    87  	// Store remain data(< 64 bytes), d.length == 0 for now.
    88  	if len(input) > 0 {
    89  		copy(d.data[:len(input)], input)
    90  		d.length = len(input)
    91  	}
    92  
    93  	return ilen, nil
    94  }
    95  
    96  // Sum64 returns the current hash.
    97  func (d *Digest) Sum64() uint64 {
    98  	if d.total <= 64 {
    99  		return Sum64WithSeed(byteToSlice(d.data, d.length), d.seed)
   100  	}
   101  
   102  	var (
   103  		i      = uintptr(d.length)
   104  		paddr  = unsafe.Pointer(&d.data)
   105  		seed   = d.seed ^ d.see1
   106  		length = d.total
   107  		a, b   uint64
   108  	)
   109  
   110  	for i > 16 {
   111  		seed = _wymix(runtimex.ReadUnaligned64(paddr)^s1, runtimex.ReadUnaligned64(add(paddr, 8))^seed)
   112  		paddr = add(paddr, 16)
   113  		i -= 16
   114  	}
   115  
   116  	// i <= 16
   117  	switch {
   118  	case i == 0:
   119  		return _wymix(s1, _wymix(s1, seed))
   120  	case i < 4:
   121  		a = uint64(*(*byte)(paddr))<<16 | uint64(*(*byte)(add(paddr, uintptr(i>>1))))<<8 | uint64(*(*byte)(add(paddr, uintptr(i-1))))
   122  		// b = 0
   123  		return _wymix(s1^uint64(length), _wymix(a^s1, seed))
   124  	case i == 4:
   125  		a = runtimex.ReadUnaligned32(paddr)
   126  		// b = 0
   127  		return _wymix(s1^uint64(length), _wymix(a^s1, seed))
   128  	case i < 8:
   129  		a = runtimex.ReadUnaligned32(paddr)
   130  		b = runtimex.ReadUnaligned32(add(paddr, i-4))
   131  		return _wymix(s1^uint64(length), _wymix(a^s1, b^seed))
   132  	case i == 8:
   133  		a = runtimex.ReadUnaligned64(paddr)
   134  		// b = 0
   135  		return _wymix(s1^uint64(length), _wymix(a^s1, seed))
   136  	default: // 8 < i <= 16
   137  		a = runtimex.ReadUnaligned64(paddr)
   138  		b = runtimex.ReadUnaligned64(add(paddr, i-8))
   139  		return _wymix(s1^uint64(length), _wymix(a^s1, b^seed))
   140  	}
   141  }
   142  
   143  // Sum appends the current hash to b and returns the resulting slice.
   144  func (d *Digest) Sum(b []byte) []byte {
   145  	s := d.Sum64()
   146  	return append(
   147  		b,
   148  		byte(s>>56),
   149  		byte(s>>48),
   150  		byte(s>>40),
   151  		byte(s>>32),
   152  		byte(s>>24),
   153  		byte(s>>16),
   154  		byte(s>>8),
   155  		byte(s),
   156  	)
   157  }
   158  
   159  func byteToSlice(b [64]byte, length int) []byte {
   160  	var res []byte
   161  	bh := (*reflect.SliceHeader)(unsafe.Pointer(&res))
   162  	bh.Data = uintptr(unsafe.Pointer(&b))
   163  	bh.Len = length
   164  	bh.Cap = length
   165  	return res
   166  }