github.com/bytedance/gopkg@v0.0.0-20240514070511-01b2cbcf35e1/internal/wyhash/digest.go (about)

     1  // Copyright 2021 ByteDance Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package wyhash
    16  
    17  import (
    18  	"reflect"
    19  	"unsafe"
    20  
    21  	"github.com/bytedance/gopkg/internal/runtimex"
    22  )
    23  
    24  type Digest struct {
    25  	initseed uint64
    26  	seed     uint64
    27  	see1     uint64
    28  	data     [64]byte
    29  	length   int
    30  	total    int
    31  }
    32  
    33  // New creates a new Digest that computes the 64-bit wyhash algorithm.
    34  func New(seed uint64) *Digest {
    35  	return &Digest{
    36  		seed:     seed,
    37  		initseed: seed,
    38  		see1:     seed,
    39  	}
    40  }
    41  
    42  func NewDefault() *Digest {
    43  	return New(DefaultSeed)
    44  }
    45  
    46  // Size always returns 8 bytes.
    47  func (d *Digest) Size() int { return 8 }
    48  
    49  // BlockSize always returns 64 bytes.
    50  func (d *Digest) BlockSize() int { return 64 }
    51  
    52  // Reset the digest, and the seed will be reset to initseed.
    53  // The initseed is the seed when digest has been created.
    54  func (d *Digest) Reset() {
    55  	d.seed = d.initseed
    56  	d.see1 = d.initseed
    57  	d.total = 0
    58  	d.length = 0
    59  }
    60  
    61  func (d *Digest) SetSeed(seed uint64)     { d.seed = seed }
    62  func (d *Digest) Seed() uint64            { return d.seed }
    63  func (d *Digest) SetInitSeed(seed uint64) { d.initseed = seed }
    64  func (d *Digest) InitSeed() uint64        { return d.initseed }
    65  
    66  // Write (via the embedded io.Writer interface) adds more data to the running hash.
    67  // It never returns an error.
    68  func (d *Digest) Write(input []byte) (int, error) {
    69  	ilen := len(input)
    70  	d.total += ilen
    71  
    72  	if d.length+ilen <= 64 {
    73  		copy(d.data[d.length:d.length+ilen], input)
    74  		d.length += ilen
    75  		return ilen, nil
    76  	}
    77  
    78  	// Consume first 64 bytes if possible.
    79  	if d.length != 0 {
    80  		inputpre := 64 - d.length // the data from input to d.data
    81  		copy(d.data[d.length:], input[:inputpre])
    82  		input = input[inputpre:] // free preceding data
    83  
    84  		paddr := unsafe.Pointer(&d.data)
    85  		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)
    86  		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)
    87  
    88  		d.length = 0 // free d.data, since it has been consumed
    89  	}
    90  
    91  	// If remain input still greater than 64.
    92  	seed, see1 := d.seed, d.see1
    93  	for len(input) > 64 {
    94  		paddr := *(*unsafe.Pointer)(unsafe.Pointer(&input))
    95  		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)
    96  		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)
    97  		input = input[64:]
    98  	}
    99  	d.seed, d.see1 = seed, see1
   100  
   101  	// Store remain data(< 64 bytes), d.length == 0 for now.
   102  	if len(input) > 0 {
   103  		copy(d.data[:len(input)], input)
   104  		d.length = len(input)
   105  	}
   106  
   107  	return ilen, nil
   108  }
   109  
   110  // Sum64 returns the current hash.
   111  func (d *Digest) Sum64() uint64 {
   112  	if d.total <= 64 {
   113  		return Sum64WithSeed(byteToSlice(d.data, d.length), d.seed)
   114  	}
   115  
   116  	var (
   117  		i      = uintptr(d.length)
   118  		paddr  = unsafe.Pointer(&d.data)
   119  		seed   = d.seed ^ d.see1
   120  		length = d.total
   121  		a, b   uint64
   122  	)
   123  
   124  	for i > 16 {
   125  		seed = _wymix(runtimex.ReadUnaligned64(paddr)^s1, runtimex.ReadUnaligned64(add(paddr, 8))^seed)
   126  		paddr = add(paddr, 16)
   127  		i -= 16
   128  	}
   129  
   130  	// i <= 16
   131  	switch {
   132  	case i == 0:
   133  		return _wymix(s1, _wymix(s1, seed))
   134  	case i < 4:
   135  		a = uint64(*(*byte)(paddr))<<16 | uint64(*(*byte)(add(paddr, uintptr(i>>1))))<<8 | uint64(*(*byte)(add(paddr, uintptr(i-1))))
   136  		// b = 0
   137  		return _wymix(s1^uint64(length), _wymix(a^s1, seed))
   138  	case i == 4:
   139  		a = runtimex.ReadUnaligned32(paddr)
   140  		// b = 0
   141  		return _wymix(s1^uint64(length), _wymix(a^s1, seed))
   142  	case i < 8:
   143  		a = runtimex.ReadUnaligned32(paddr)
   144  		b = runtimex.ReadUnaligned32(add(paddr, i-4))
   145  		return _wymix(s1^uint64(length), _wymix(a^s1, b^seed))
   146  	case i == 8:
   147  		a = runtimex.ReadUnaligned64(paddr)
   148  		// b = 0
   149  		return _wymix(s1^uint64(length), _wymix(a^s1, seed))
   150  	default: // 8 < i <= 16
   151  		a = runtimex.ReadUnaligned64(paddr)
   152  		b = runtimex.ReadUnaligned64(add(paddr, i-8))
   153  		return _wymix(s1^uint64(length), _wymix(a^s1, b^seed))
   154  	}
   155  }
   156  
   157  // Sum appends the current hash to b and returns the resulting slice.
   158  func (d *Digest) Sum(b []byte) []byte {
   159  	s := d.Sum64()
   160  	return append(
   161  		b,
   162  		byte(s>>56),
   163  		byte(s>>48),
   164  		byte(s>>40),
   165  		byte(s>>32),
   166  		byte(s>>24),
   167  		byte(s>>16),
   168  		byte(s>>8),
   169  		byte(s),
   170  	)
   171  }
   172  
   173  func byteToSlice(b [64]byte, length int) []byte {
   174  	var res []byte
   175  	bh := (*reflect.SliceHeader)(unsafe.Pointer(&res))
   176  	bh.Data = uintptr(unsafe.Pointer(&b))
   177  	bh.Len = length
   178  	bh.Cap = length
   179  	return res
   180  }