]> Cypherpunks.ru repositories - gostls13.git/commitdiff
crypto/sha1: add WriteString and WriteByte method
authorIan Lance Taylor <iant@golang.org>
Tue, 11 Apr 2023 22:28:23 +0000 (15:28 -0700)
committerGopher Robot <gobot@golang.org>
Tue, 25 Apr 2023 22:06:06 +0000 (22:06 +0000)
This can reduce allocations when hashing a string or byte
rather than []byte.

For #38776

Change-Id: I7c1fbdf15abf79d2faf360f75adf4bc550a607e9
Reviewed-on: https://go-review.googlesource.com/c/go/+/483815
TryBot-Result: Gopher Robot <gobot@golang.org>
Auto-Submit: Ian Lance Taylor <iant@google.com>
Run-TryBot: Ian Lance Taylor <iant@golang.org>
Run-TryBot: Ian Lance Taylor <iant@google.com>
Reviewed-by: Bryan Mills <bcmills@google.com>
Reviewed-by: Ian Lance Taylor <iant@google.com>
Reviewed-by: Joel Sing <joel@sing.id.au>
14 files changed:
src/crypto/internal/boring/sha.go
src/crypto/sha1/sha1.go
src/crypto/sha1/sha1_test.go
src/crypto/sha1/sha1block.go
src/crypto/sha1/sha1block_386.s
src/crypto/sha1/sha1block_amd64.go
src/crypto/sha1/sha1block_amd64.s
src/crypto/sha1/sha1block_arm.s
src/crypto/sha1/sha1block_arm64.go
src/crypto/sha1/sha1block_arm64.s
src/crypto/sha1/sha1block_decl.go
src/crypto/sha1/sha1block_generic.go
src/crypto/sha1/sha1block_s390x.go
src/crypto/sha1/sha1block_s390x.s

index b7843674a57a0a83e7136b11e226a9312e2409e2..c9772aa6f1cf214ac4fa103e426b386f940670df 100644 (file)
@@ -145,6 +145,20 @@ func (h *sha1Hash) Write(p []byte) (int, error) {
        return len(p), nil
 }
 
+func (h *sha1Hash) WriteString(s string) (int, error) {
+       if len(s) > 0 && C._goboringcrypto_SHA1_Update(h.noescapeCtx(), unsafe.Pointer(unsafe.StringData(s)), C.size_t(len(s))) == 0 {
+               panic("boringcrypto: SHA1_Update failed")
+       }
+       return len(s), nil
+}
+
+func (h *sha1Hash) WriteByte(c byte) error {
+       if C._goboringcrypto_SHA1_Update(h.noescapeCtx(), unsafe.Pointer(&c), 1) == 0 {
+               panic("boringcrypto: SHA1_Update failed")
+       }
+       return nil
+}
+
 func (h0 *sha1Hash) sum(dst []byte) []byte {
        h := *h0 // make copy so future Write+Sum is valid
        if C._goboringcrypto_SHA1_Final((*C.uint8_t)(noescape(unsafe.Pointer(&h.out[0]))), h.noescapeCtx()) == 0 {
index 43ab72a48548d9b31fca5f6caf9c8f33440b8fd5..19f176788292204976dca93ff990e74a8a5416b7 100644 (file)
@@ -120,18 +120,10 @@ func (d *digest) Size() int { return Size }
 func (d *digest) BlockSize() int { return BlockSize }
 
 func (d *digest) Write(p []byte) (nn int, err error) {
-       boringUnreachable()
        nn = len(p)
        d.len += uint64(nn)
-       if d.nx > 0 {
-               n := copy(d.x[d.nx:], p)
-               d.nx += n
-               if d.nx == chunk {
-                       block(d, d.x[:])
-                       d.nx = 0
-               }
-               p = p[n:]
-       }
+       n := fillChunk(d, p)
+       p = p[n:]
        if len(p) >= chunk {
                n := len(p) &^ (chunk - 1)
                block(d, p[:n])
@@ -143,6 +135,55 @@ func (d *digest) Write(p []byte) (nn int, err error) {
        return
 }
 
+func (d *digest) WriteString(s string) (nn int, err error) {
+       nn = len(s)
+       d.len += uint64(nn)
+       n := fillChunk(d, s)
+
+       // This duplicates the code in Write, except that it calls
+       // blockString rather than block. It would be nicer to pass
+       // in a func, but as of this writing (Go 1.20) that causes
+       // memory allocations that we want to avoid.
+
+       s = s[n:]
+       if len(s) >= chunk {
+               n := len(s) &^ (chunk - 1)
+               blockString(d, s[:n])
+               s = s[n:]
+       }
+       if len(s) > 0 {
+               d.nx = copy(d.x[:], s)
+       }
+       return
+}
+
+// fillChunk fills the remainder of the current chunk, if any.
+func fillChunk[S []byte | string](d *digest, p S) int {
+       boringUnreachable()
+       if d.nx == 0 {
+               return 0
+       }
+       n := copy(d.x[d.nx:], p)
+       d.nx += n
+       if d.nx == chunk {
+               block(d, d.x[:])
+               d.nx = 0
+       }
+       return n
+}
+
+func (d *digest) WriteByte(c byte) error {
+       boringUnreachable()
+       d.len++
+       d.x[d.nx] = c
+       d.nx++
+       if d.nx == chunk {
+               block(d, d.x[:])
+               d.nx = 0
+       }
+       return nil
+}
+
 func (d *digest) Sum(in []byte) []byte {
        boringUnreachable()
        // Make a copy of d so that caller can keep writing and summing.
index 85ed12609154ff36ebddaedcbe527044be76f87a..2f0980adaa715c76211be454446073e6a4ad46f9 100644 (file)
@@ -92,6 +92,14 @@ func TestGolden(t *testing.T) {
                        }
                        c.Reset()
                }
+               bw := c.(io.ByteWriter)
+               for i := 0; i < len(g.in); i++ {
+                       bw.WriteByte(g.in[i])
+               }
+               s = fmt.Sprintf("%x", c.Sum(nil))
+               if s != g.out {
+                       t.Errorf("sha1[WriteByte](%s) = %s want %s", g.in, s, g.out)
+               }
        }
 }
 
@@ -221,7 +229,8 @@ func TestAllocations(t *testing.T) {
        if boring.Enabled {
                t.Skip("BoringCrypto doesn't allocate the same way as stdlib")
        }
-       in := []byte("hello, world!")
+       const ins = "hello, world!"
+       in := []byte(ins)
        out := make([]byte, 0, Size)
        h := New()
        n := int(testing.AllocsPerRun(10, func() {
@@ -232,6 +241,28 @@ func TestAllocations(t *testing.T) {
        if n > 0 {
                t.Errorf("allocs = %d, want 0", n)
        }
+
+       sw := h.(io.StringWriter)
+       n = int(testing.AllocsPerRun(10, func() {
+               h.Reset()
+               sw.WriteString(ins)
+               out = h.Sum(out[:0])
+       }))
+       if n > 0 {
+               t.Errorf("string allocs = %d, want 0", n)
+       }
+
+       bw := h.(io.ByteWriter)
+       n = int(testing.AllocsPerRun(10, func() {
+               h.Reset()
+               for _, b := range in {
+                       bw.WriteByte(b)
+               }
+               out = h.Sum(out[:0])
+       }))
+       if n > 0 {
+               t.Errorf("byte allocs = %d, want 0", n)
+       }
 }
 
 var bench = New()
index 1c1a7c5f31c16413b949986f2793da1275669b5d..0b332859dfcbd5053b5f5e0d64c5b9b342e0e494 100644 (file)
@@ -17,7 +17,7 @@ const (
 
 // blockGeneric is a portable, pure Go version of the SHA-1 block step.
 // It's used by sha1block_generic.go and tests.
-func blockGeneric(dig *digest, p []byte) {
+func blockGeneric[S []byte | string](dig *digest, p S) {
        var w [16]uint32
 
        h0, h1, h2, h3, h4 := dig.h[0], dig.h[1], dig.h[2], dig.h[3], dig.h[4]
index 34d023d424c0ff30e58b462a9161f8f5c73a9c73..9421b4ebd69b60abbb250a4a4d973e46280b6995 100644 (file)
        FUNC4(a, b, c, d, e); \
        MIX(a, b, c, d, e, 0xCA62C1D6)
 
-// func block(dig *digest, p []byte)
-TEXT ·block(SB),NOSPLIT,$92-16
+// func doBlock(dig *digest, p *byte, n int)
+TEXT ·doBlock(SB),NOSPLIT,$92-12
        MOVL    dig+0(FP),      BP
        MOVL    p+4(FP),        SI
-       MOVL    p_len+8(FP),    DX
+       MOVL    n+8(FP),        DX
        SHRL    $6,             DX
        SHLL    $6,             DX
 
index 039813d7dc3fea7cc224a28fbc6e71c10efc02a7..528d65dd71bce68769b530d084bb926501b28d5d 100644 (file)
@@ -4,19 +4,22 @@
 
 package sha1
 
-import "internal/cpu"
+import (
+       "internal/cpu"
+       "unsafe"
+)
 
 //go:noescape
-func blockAVX2(dig *digest, p []byte)
+func blockAVX2(dig *digest, p *byte, n int)
 
 //go:noescape
-func blockAMD64(dig *digest, p []byte)
+func blockAMD64(dig *digest, p *byte, n int)
 
 var useAVX2 = cpu.X86.HasAVX2 && cpu.X86.HasBMI1 && cpu.X86.HasBMI2
 
 func block(dig *digest, p []byte) {
        if useAVX2 && len(p) >= 256 {
-               // blockAVX2 calculates sha1 for 2 block per iteration
+               // blockAVX2 calculates sha1 for 2 blocks per iteration
                // it also interleaves precalculation for next block.
                // So it may read up-to 192 bytes past end of p
                // We may add checks inside blockAVX2, but this will
@@ -26,9 +29,25 @@ func block(dig *digest, p []byte) {
                if safeLen%128 != 0 {
                        safeLen -= 64
                }
-               blockAVX2(dig, p[:safeLen])
-               blockAMD64(dig, p[safeLen:])
+               blockAVX2(dig, unsafe.SliceData(p), safeLen)
+               pRem := p[safeLen:]
+               blockAMD64(dig, unsafe.SliceData(pRem), len(pRem))
        } else {
-               blockAMD64(dig, p)
+               blockAMD64(dig, unsafe.SliceData(p), len(p))
+       }
+}
+
+// blockString is a duplicate of block that takes a string.
+func blockString(dig *digest, s string) {
+       if useAVX2 && len(s) >= 256 {
+               safeLen := len(s) - 128
+               if safeLen%128 != 0 {
+                       safeLen -= 64
+               }
+               blockAVX2(dig, unsafe.StringData(s), safeLen)
+               sRem := s[safeLen:]
+               blockAMD64(dig, unsafe.StringData(sRem), len(sRem))
+       } else {
+               blockAMD64(dig, unsafe.StringData(s), len(s))
        }
 }
index 9bdf24cf49f24e5b9b59fe3e1920ca50a6e3ee3a..23b47dac90648e0339c52101389ad9a9574a8e70 100644 (file)
        FUNC4(a, b, c, d, e); \
        MIX(a, b, c, d, e, 0xCA62C1D6)
 
-TEXT ·blockAMD64(SB),NOSPLIT,$64-32
+TEXT ·blockAMD64(SB),NOSPLIT,$64-24
        MOVQ    dig+0(FP),      BP
-       MOVQ    p_base+8(FP),   SI
-       MOVQ    p_len+16(FP),   DX
+       MOVQ    p+8(FP),        SI
+       MOVQ    n+16(FP),       DX
        SHRQ    $6,             DX
        SHLQ    $6,             DX
 
@@ -1430,11 +1430,11 @@ begin: \
 
 
 
-TEXT ·blockAVX2(SB),$1408-32
+TEXT ·blockAVX2(SB),$1408-24
 
        MOVQ    dig+0(FP),      DI
-       MOVQ    p_base+8(FP),   SI
-       MOVQ    p_len+16(FP),   DX
+       MOVQ    p+8(FP),        SI
+       MOVQ    n+16(FP),       DX
        SHRQ    $6,             DX
        SHLQ    $6,             DX
 
index 2236533ab4d9ef19a589c9624e5961ef3de2982a..db651db3625158a8980355d7ad75049757eb17a3 100644 (file)
 #define Rctr   R12     // loop counter
 #define Rw     R14             // point to w buffer
 
-// func block(dig *digest, p []byte)
+// func doBlock(dig *digest, p *byte, n int)
 // 0(FP) is *digest
 // 4(FP) is p.array (struct Slice)
 // 8(FP) is p.len
-//12(FP) is p.cap
 //
 // Stack frame
 #define p_end  end-4(SP)               // pointer to the end of data
        MIX(Ra, Rb, Rc, Rd, Re)
 
 
-// func block(dig *digest, p []byte)
-TEXT   ·block(SB), 0, $352-16
+// func doBlock(dig *digest, p *byte, n int)
+TEXT   ·doBlock(SB), 0, $352-12
        MOVW    p+4(FP), Rdata  // pointer to the data
-       MOVW    p_len+8(FP), Rt0        // number of bytes
+       MOVW    n+8(FP), Rt0    // number of bytes
        ADD     Rdata, Rt0
        MOVW    Rt0, p_end      // pointer to end of data
 
index 08d3df0000e686157952b2ffe20b2e3e87432a4f..846c88226f074679729157a0f107d5ad4f1bc81e 100644 (file)
@@ -4,7 +4,10 @@
 
 package sha1
 
-import "internal/cpu"
+import (
+       "internal/cpu"
+       "unsafe"
+)
 
 var k = []uint32{
        0x5A827999,
@@ -14,13 +17,22 @@ var k = []uint32{
 }
 
 //go:noescape
-func sha1block(h []uint32, p []byte, k []uint32)
+func sha1block(h []uint32, p *byte, n int, k []uint32)
 
 func block(dig *digest, p []byte) {
        if !cpu.ARM64.HasSHA1 {
                blockGeneric(dig, p)
        } else {
                h := dig.h[:]
-               sha1block(h, p, k)
+               sha1block(h, unsafe.SliceData(p), len(p), k)
+       }
+}
+
+func blockString(dig *digest, s string) {
+       if !cpu.ARM64.HasSHA1 {
+               blockGeneric(dig, s)
+       } else {
+               h := dig.h[:]
+               sha1block(h, unsafe.StringData(s), len(s), k)
        }
 }
index d56838464ddba30ac75cd0844e3721aa61d5d097..e5e32437350c25d4aa0809effb1964fa2b9b4e19 100644 (file)
        SHA1H   V3, V1 \
        VMOV    V2.B16, V3.B16
 
-// func sha1block(h []uint32, p []byte, k []uint32)
+// func sha1block(h []uint32, p *byte, n int, k []uint32)
 TEXT ·sha1block(SB),NOSPLIT,$0
        MOVD    h_base+0(FP), R0                             // hash value first address
-       MOVD    p_base+24(FP), R1                            // message first address
-       MOVD    k_base+48(FP), R2                            // k constants first address
-       MOVD    p_len+32(FP), R3                             // message length
+       MOVD    p+24(FP), R1                                 // message first address
+       MOVD    k_base+40(FP), R2                            // k constants first address
+       MOVD    n+32(FP), R3                                 // message length
        VLD1.P  16(R0), [V0.S4]
        FMOVS   (R0), F20
        SUB     $16, R0, R0
index 8e20401c14c406b55f01ed6657b47ebbec0f7fbb..9ef8709637ccea50e1fc4b062b7758995f502cb6 100644 (file)
@@ -6,5 +6,15 @@
 
 package sha1
 
+import "unsafe"
+
 //go:noescape
-func block(dig *digest, p []byte)
+func doBlock(dig *digest, p *byte, n int)
+
+func block(dig *digest, p []byte) {
+       doBlock(dig, unsafe.SliceData(p), len(p))
+}
+
+func blockString(dig *digest, s string) {
+       doBlock(dig, unsafe.StringData(s), len(s))
+}
index ba35155d0b92ef4d4c801f8dea8d70d5ec36775d..4eb489f01a1636051ebc497997680a703986050b 100644 (file)
@@ -9,3 +9,7 @@ package sha1
 func block(dig *digest, p []byte) {
        blockGeneric(dig, p)
 }
+
+func blockString(dig *digest, s string) {
+       blockGeneric(dig, s)
+}
index 446bf5d36e5943a74dd9430abe1d5c35f79796f0..06c972d3af9d32b9f9ce90f3360829b2fe564d13 100644 (file)
@@ -4,6 +4,13 @@
 
 package sha1
 
-import "internal/cpu"
+import (
+       "internal/cpu"
+       "unsafe"
+)
 
 var useAsm = cpu.S390X.HasSHA1
+
+func doBlockGeneric(dig *digest, p *byte, n int) {
+       blockGeneric(dig, unsafe.String(p, n))
+}
index 6ba6883cc38fc6576b7254330ce780705eb73661..3d082342ff18fc70ac5759e41834fb0082586d1d 100644 (file)
@@ -4,10 +4,10 @@
 
 #include "textflag.h"
 
-// func block(dig *digest, p []byte)
-TEXT ·block(SB), NOSPLIT|NOFRAME, $0-32
+// func doBlock(dig *digest, p *byte, n int)
+TEXT ·doBlock(SB), NOSPLIT|NOFRAME, $0-24
        MOVBZ  ·useAsm(SB), R4
-       LMG    dig+0(FP), R1, R3            // R2 = &p[0], R3 = len(p)
+       LMG    dig+0(FP), R1, R3            // R2 = p, R3 = n
        MOVBZ  $1, R0                       // SHA-1 function code
        CMPBEQ R4, $0, generic
 
@@ -17,4 +17,4 @@ loop:
        RET
 
 generic:
-       BR ·blockGeneric(SB)
+       BR ·doBlockGeneric(SB)