Skip to content

Commit 776275e

Browse files
authored
Use scalar functions when less traffic (#24)
Switch to scalar assembly when less than 3 lanes are filled. This brings us very close to `crypto/md5` in cases where only a single lane is populated. When there are 2 lanes filled we use 2 goroutines with the scalar code and above that we switch to SIMD. Before, with a single writer: ``` BenchmarkAvx2SingleWriter/32KB-32 14686 80893 ns/op 405.08 MB/s 976 B/op 8 allocs/op BenchmarkAvx2SingleWriter/64KB-32 7498 162843 ns/op 402.45 MB/s 1840 B/op 15 allocs/op BenchmarkAvx2SingleWriter/128KB-32 3636 327558 ns/op 400.15 MB/s 3568 B/op 29 allocs/op BenchmarkAvx2SingleWriter/256KB-32 1845 650406 ns/op 403.05 MB/s 7024 B/op 57 allocs/op BenchmarkAvx2SingleWriter/512KB-32 922 1295010 ns/op 404.85 MB/s 13937 B/op 113 allocs/op BenchmarkAvx2SingleWriter/1MB-32 463 2598272 ns/op 403.57 MB/s 27765 B/op 225 allocs/op BenchmarkAvx2SingleWriter/2MB-32 231 5164500 ns/op 406.07 MB/s 55411 B/op 449 allocs/op BenchmarkAvx2SingleWriter/4MB-32 100 10170000 ns/op 412.42 MB/s 110709 B/op 897 allocs/op BenchmarkAvx2SingleWriter/8MB-32 56 20357161 ns/op 412.07 MB/s 221305 B/op 1793 allocs/op ``` After: ``` BenchmarkAvx2SingleWriter/32KB-32 26785 44353 ns/op 738.80 MB/s 112 B/op 1 allocs/op BenchmarkAvx2SingleWriter/64KB-32 13682 87853 ns/op 745.98 MB/s 112 B/op 1 allocs/op BenchmarkAvx2SingleWriter/128KB-32 7058 175829 ns/op 745.45 MB/s 112 B/op 1 allocs/op BenchmarkAvx2SingleWriter/256KB-32 3428 346558 ns/op 756.42 MB/s 112 B/op 1 allocs/op BenchmarkAvx2SingleWriter/512KB-32 1713 686515 ns/op 763.69 MB/s 112 B/op 1 allocs/op BenchmarkAvx2SingleWriter/1MB-32 874 1366132 ns/op 767.55 MB/s 112 B/op 1 allocs/op BenchmarkAvx2SingleWriter/2MB-32 439 2740318 ns/op 765.30 MB/s 112 B/op 1 allocs/op BenchmarkAvx2SingleWriter/4MB-32 220 5431817 ns/op 772.17 MB/s 113 B/op 1 allocs/op BenchmarkAvx2SingleWriter/8MB-32 100 10840002 ns/op 773.86 MB/s 116 B/op 1 allocs/op ``` Compare to pure crypto/md5: ``` BenchmarkCryptoMd5/32KB-32 30612 39004 ns/op 840.11 MB/s 0 B/op 0 allocs/op BenchmarkCryptoMd5/64KB-32 15285 77985 ns/op 840.37 MB/s 0 B/op 0 allocs/op BenchmarkCryptoMd5/128KB-32 7498 156175 ns/op 839.26 MB/s 0 B/op 0 allocs/op BenchmarkCryptoMd5/256KB-32 3870 310336 ns/op 844.71 MB/s 0 B/op 0 allocs/op BenchmarkCryptoMd5/512KB-32 1874 623266 ns/op 841.19 MB/s 0 B/op 0 allocs/op BenchmarkCryptoMd5/1MB-32 960 1243750 ns/op 843.08 MB/s 0 B/op 0 allocs/op BenchmarkCryptoMd5/2MB-32 480 2489588 ns/op 842.37 MB/s 0 B/op 0 allocs/op ``` After optimizing the assembly: ``` BenchmarkAvx2SingleWriter BenchmarkAvx2SingleWriter/32KB-32 28570 41941 ns/op 781.29 MB/s 0 B/op 0 allocs/op BenchmarkAvx2SingleWriter/64KB-32 14388 83055 ns/op 789.06 MB/s 0 B/op 0 allocs/op BenchmarkAvx2SingleWriter/128KB-32 7500 167734 ns/op 781.43 MB/s 0 B/op 0 allocs/op BenchmarkAvx2SingleWriter/256KB-32 3636 332508 ns/op 788.38 MB/s 1 B/op 0 allocs/op BenchmarkAvx2SingleWriter/512KB-32 1818 659667 ns/op 794.78 MB/s 2 B/op 0 allocs/op BenchmarkAvx2SingleWriter/1MB-32 915 1315847 ns/op 796.88 MB/s 5 B/op 0 allocs/op BenchmarkAvx2SingleWriter/2MB-32 457 2621787 ns/op 799.89 MB/s 11 B/op 0 allocs/op BenchmarkAvx2SingleWriter/4MB-32 229 5213972 ns/op 804.44 MB/s 22 B/op 0 allocs/op BenchmarkAvx2SingleWriter/8MB-32 100 10409999 ns/op 805.82 MB/s 51 B/op 0 allocs/op ```
1 parent 263cf43 commit 776275e

File tree

10 files changed

+1207
-133
lines changed

10 files changed

+1207
-133
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,8 @@ BenchmarkParallel/8MB-4 2182.48 17252.88 7.91x
116116

117117
These measurements were performed on AWS EC2 instance of type `c5.xlarge` equipped with a Xeon Platinum 8124M CPU at 3.0 GHz.
118118

119+
If only one or two inputs are available the scalar calculation method will be used for the
120+
optimal speed in these cases.
119121

120122
## Operation
121123

_gen/gen.go

Lines changed: 299 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,299 @@
1+
package main
2+
3+
//go:generate go run gen.go -out ../md5block_amd64.s -stubs ../md5block_amd64.go -pkg=md5simd
4+
5+
import (
6+
x "github.com/mmcloughlin/avo/build"
7+
"github.com/mmcloughlin/avo/buildtags"
8+
o "github.com/mmcloughlin/avo/operand"
9+
"github.com/mmcloughlin/avo/reg"
10+
)
11+
12+
// AMD:
13+
// 2025 BMI2 :RORX r32, r32, r32 L: 0.29ns= 1.0c T: 0.15ns= 0.50c
14+
// 271 X86 :ROL r32, imm8 L: 0.29ns= 1.0c T: 0.15ns= 0.50c
15+
//
16+
// INTEL:
17+
// 271 X86 :ROL r32, imm8 L: 0.27ns= 1.0c T: 0.14ns= 0.50c
18+
// 2025 BMI2 :RORX r32, r32, r32 L: 0.27ns= 1.0c T: 0.14ns= 0.50c
19+
20+
// Neither appear to have any gains
21+
// Don't bother with BMI2
22+
const useROLX = false
23+
24+
func ROLL(imm int, gpr reg.GPVirtual) {
25+
if useROLX {
26+
x.RORXL(o.U8(32-imm), gpr, gpr)
27+
} else {
28+
x.ROLL(o.U8(imm), gpr)
29+
}
30+
}
31+
32+
// AMD:
33+
// 154 X86 :XOR r32, r32 L: 0.06ns= 0.2c T: 0.06ns= 0.25c
34+
// 166 X86 :NOT r32 L: 0.26ns= 1.0c T: 0.11ns= 0.43c
35+
//
36+
// INTEL:
37+
// Inst 166 X86 : NOT r32 L: 0.45ns= 1.0c T: 0.11ns= 0.25c
38+
// Inst 154 X86 : XOR r32, r32 L: 0.11ns= 0.2c T: 0.11ns= 0.25c
39+
func NOTL(gpr, ones reg.GPVirtual) {
40+
// Use XOR
41+
if false {
42+
x.NOTL(gpr)
43+
} else {
44+
x.XORL(ones, gpr)
45+
}
46+
}
47+
48+
func main() {
49+
x.Constraint(buildtags.Not("appengine").ToConstraint())
50+
x.Constraint(buildtags.Not("noasm").ToConstraint())
51+
x.Constraint(buildtags.Term("gc").ToConstraint())
52+
x.TEXT("blockScalar", 0, "func(dig *[4]uint32, p []byte)")
53+
x.Doc("Encode p to digest")
54+
x.Pragma("noescape")
55+
56+
srcLen := x.Load(x.Param("p").Len(), x.GP64())
57+
digest := x.Load(x.Param("dig"), x.GP64())
58+
src := x.Load(x.Param("p").Base(), x.GP64())
59+
60+
x.SHRQ(o.U8(6), srcLen)
61+
x.SHLQ(o.U8(6), srcLen)
62+
end := x.GP64()
63+
x.LEAQ(o.Mem{Base: src, Index: srcLen, Scale: 1}, end)
64+
x.CMPQ(src, end)
65+
x.JEQ(o.LabelRef("end"))
66+
var dig [4]reg.GPVirtual
67+
for i := range dig {
68+
dig[i] = x.GP32()
69+
x.MOVL(o.Mem{Base: digest, Disp: i * 4}, dig[i])
70+
}
71+
AX, BX, CX, DX := dig[0], dig[1], dig[2], dig[3]
72+
73+
// Keep ones in a register
74+
ones := x.GP32()
75+
x.MOVL(o.U32(0xffffffff), ones)
76+
77+
x.Label("loop")
78+
var block [4]reg.VecVirtual
79+
R8, R9 := x.GP32(), x.GP32()
80+
// load source. Skipped if idx < 0
81+
var loadSrc func(idx int, dst reg.GPVirtual)
82+
// Appears slower.
83+
const useXMM = false
84+
if useXMM {
85+
for i := range block {
86+
block[i] = x.XMM()
87+
x.MOVUPS(o.Mem{Base: src, Disp: 16 * i}, block[i])
88+
}
89+
// load source. Skipped if idx < 0
90+
loadSrc = func(idx int, dst reg.GPVirtual) {
91+
if idx < 0 {
92+
return
93+
}
94+
95+
// 4 per block
96+
xmm := block[idx/4]
97+
x.PEXTRD(o.U8(idx&3), xmm, dst)
98+
}
99+
} else {
100+
loadSrc = func(idx int, dst reg.GPVirtual) {
101+
if idx < 0 {
102+
return
103+
}
104+
x.MOVL(o.Mem{Base: src, Disp: idx * 4}, dst)
105+
}
106+
}
107+
const useLEA = false
108+
loadSrc(0, R8)
109+
x.MOVL(DX, R9)
110+
111+
// Copy digest
112+
R12, R13, R14, R15 := x.GP32(), x.GP32(), x.GP32(), x.GP32()
113+
x.MOVL(AX, R12)
114+
x.MOVL(BX, R13)
115+
x.MOVL(CX, R14)
116+
x.MOVL(DX, R15)
117+
118+
// ROUND 1:
119+
x.Comment("ROUND1")
120+
ROUND1 := func(a, b, c, d reg.GPVirtual, index, con, shift int) {
121+
x.XORL(c, R9)
122+
if useLEA {
123+
x.LEAL(o.Mem{Base: a, Disp: con, Index: R8, Scale: 1}, a)
124+
} else {
125+
x.ADDL(o.U32(con), a)
126+
x.ADDL(R8, a)
127+
}
128+
x.ANDL(b, R9)
129+
x.XORL(d, R9)
130+
loadSrc(index, R8)
131+
x.ADDL(R9, a)
132+
ROLL(shift, a)
133+
x.MOVL(c, R9)
134+
x.ADDL(b, a)
135+
}
136+
137+
ROUND1(AX, BX, CX, DX, 1, 0xd76aa478, 7)
138+
ROUND1(DX, AX, BX, CX, 2, 0xe8c7b756, 12)
139+
ROUND1(CX, DX, AX, BX, 3, 0x242070db, 17)
140+
ROUND1(BX, CX, DX, AX, 4, 0xc1bdceee, 22)
141+
ROUND1(AX, BX, CX, DX, 5, 0xf57c0faf, 7)
142+
ROUND1(DX, AX, BX, CX, 6, 0x4787c62a, 12)
143+
ROUND1(CX, DX, AX, BX, 7, 0xa8304613, 17)
144+
ROUND1(BX, CX, DX, AX, 8, 0xfd469501, 22)
145+
ROUND1(AX, BX, CX, DX, 9, 0x698098d8, 7)
146+
ROUND1(DX, AX, BX, CX, 10, 0x8b44f7af, 12)
147+
ROUND1(CX, DX, AX, BX, 11, 0xffff5bb1, 17)
148+
ROUND1(BX, CX, DX, AX, 12, 0x895cd7be, 22)
149+
ROUND1(AX, BX, CX, DX, 13, 0x6b901122, 7)
150+
ROUND1(DX, AX, BX, CX, 14, 0xfd987193, 12)
151+
ROUND1(CX, DX, AX, BX, 15, 0xa679438e, 17)
152+
// adjusted to load index 1
153+
ROUND1(BX, CX, DX, AX, 1, 0x49b40821, 22)
154+
155+
x.Comment("ROUND2")
156+
x.MOVL(DX, R9)
157+
R10 := x.GP32()
158+
x.MOVL(DX, R10)
159+
160+
ROUND2 := func(a, b, c, d reg.GPVirtual, index, con, shift int) {
161+
NOTL(R9, ones)
162+
if useLEA {
163+
x.LEAL(o.Mem{Base: a, Disp: con, Index: R8, Scale: 1}, a)
164+
} else {
165+
x.ADDL(o.U32(con), a)
166+
x.ADDL(R8, a)
167+
}
168+
169+
x.ANDL(b, R10)
170+
x.ANDL(c, R9)
171+
loadSrc(index, R8)
172+
x.ORL(R9, R10)
173+
x.MOVL(c, R9)
174+
x.ADDL(R10, a)
175+
x.MOVL(c, R10)
176+
ROLL(shift, a)
177+
x.ADDL(b, a)
178+
}
179+
ROUND2(AX, BX, CX, DX, 6, 0xf61e2562, 5)
180+
ROUND2(DX, AX, BX, CX, 11, 0xc040b340, 9)
181+
ROUND2(CX, DX, AX, BX, 0, 0x265e5a51, 14)
182+
ROUND2(BX, CX, DX, AX, 5, 0xe9b6c7aa, 20)
183+
ROUND2(AX, BX, CX, DX, 10, 0xd62f105d, 5)
184+
ROUND2(DX, AX, BX, CX, 15, 0x2441453, 9)
185+
ROUND2(CX, DX, AX, BX, 4, 0xd8a1e681, 14)
186+
ROUND2(BX, CX, DX, AX, 9, 0xe7d3fbc8, 20)
187+
ROUND2(AX, BX, CX, DX, 14, 0x21e1cde6, 5)
188+
ROUND2(DX, AX, BX, CX, 3, 0xc33707d6, 9)
189+
ROUND2(CX, DX, AX, BX, 8, 0xf4d50d87, 14)
190+
ROUND2(BX, CX, DX, AX, 13, 0x455a14ed, 20)
191+
ROUND2(AX, BX, CX, DX, 2, 0xa9e3e905, 5)
192+
ROUND2(DX, AX, BX, CX, 7, 0xfcefa3f8, 9)
193+
ROUND2(CX, DX, AX, BX, 12, 0x676f02d9, 14)
194+
// Adjusted to load index 5
195+
ROUND2(BX, CX, DX, AX, 5, 0x8d2a4c8a, 20)
196+
197+
x.Comment("ROUND3")
198+
x.MOVL(CX, R9)
199+
ROUND3 := func(a, b, c, d reg.GPVirtual, index, con, shift int) {
200+
// LEAL const(a)(R8*1), a; \
201+
if useLEA {
202+
x.LEAL(o.Mem{Base: a, Disp: con, Index: R8, Scale: 1}, a)
203+
} else {
204+
x.ADDL(o.U32(con), a)
205+
x.ADDL(R8, a)
206+
}
207+
loadSrc(index, R8)
208+
209+
x.XORL(d, R9)
210+
x.XORL(b, R9)
211+
x.ADDL(R9, a)
212+
ROLL(shift, a)
213+
x.MOVL(b, R9)
214+
x.ADDL(b, a)
215+
}
216+
217+
ROUND3(AX, BX, CX, DX, 8, 0xfffa3942, 4)
218+
ROUND3(DX, AX, BX, CX, 11, 0x8771f681, 11)
219+
ROUND3(CX, DX, AX, BX, 14, 0x6d9d6122, 16)
220+
ROUND3(BX, CX, DX, AX, 1, 0xfde5380c, 23)
221+
ROUND3(AX, BX, CX, DX, 4, 0xa4beea44, 4)
222+
ROUND3(DX, AX, BX, CX, 7, 0x4bdecfa9, 11)
223+
ROUND3(CX, DX, AX, BX, 10, 0xf6bb4b60, 16)
224+
ROUND3(BX, CX, DX, AX, 13, 0xbebfbc70, 23)
225+
ROUND3(AX, BX, CX, DX, 0, 0x289b7ec6, 4)
226+
ROUND3(DX, AX, BX, CX, 3, 0xeaa127fa, 11)
227+
ROUND3(CX, DX, AX, BX, 6, 0xd4ef3085, 16)
228+
ROUND3(BX, CX, DX, AX, 9, 0x4881d05, 23)
229+
ROUND3(AX, BX, CX, DX, 12, 0xd9d4d039, 4)
230+
ROUND3(DX, AX, BX, CX, 15, 0xe6db99e5, 11)
231+
ROUND3(CX, DX, AX, BX, 2, 0x1fa27cf8, 16)
232+
ROUND3(BX, CX, DX, AX, 0, 0xc4ac5665, 23)
233+
234+
// Use extra reg for constant
235+
x.Comment("ROUND4")
236+
x.MOVL(ones, R9)
237+
x.XORL(DX, R9)
238+
ROUND4 := func(a, b, c, d reg.GPVirtual, index, con, shift int) {
239+
// LEAL const(a)(R8*1), a; \
240+
if useLEA {
241+
x.LEAL(o.Mem{Base: a, Disp: con, Index: R8, Scale: 1}, a)
242+
} else {
243+
x.ADDL(o.U32(con), a)
244+
x.ADDL(R8, a)
245+
}
246+
x.ORL(b, R9)
247+
x.XORL(c, R9)
248+
x.ADDL(R9, a)
249+
loadSrc(index, R8)
250+
if index >= 0 {
251+
x.MOVL(ones, R9)
252+
}
253+
ROLL(shift, a)
254+
if index >= 0 {
255+
x.XORL(c, R9)
256+
}
257+
x.ADDL(b, a)
258+
}
259+
260+
ROUND4(AX, BX, CX, DX, 7, 0xf4292244, 6)
261+
ROUND4(DX, AX, BX, CX, 14, 0x432aff97, 10)
262+
ROUND4(CX, DX, AX, BX, 5, 0xab9423a7, 15)
263+
ROUND4(BX, CX, DX, AX, 12, 0xfc93a039, 21)
264+
ROUND4(AX, BX, CX, DX, 3, 0x655b59c3, 6)
265+
ROUND4(DX, AX, BX, CX, 10, 0x8f0ccc92, 10)
266+
ROUND4(CX, DX, AX, BX, 1, 0xffeff47d, 15)
267+
ROUND4(BX, CX, DX, AX, 8, 0x85845dd1, 21)
268+
ROUND4(AX, BX, CX, DX, 15, 0x6fa87e4f, 6)
269+
ROUND4(DX, AX, BX, CX, 6, 0xfe2ce6e0, 10)
270+
ROUND4(CX, DX, AX, BX, 13, 0xa3014314, 15)
271+
ROUND4(BX, CX, DX, AX, 4, 0x4e0811a1, 21)
272+
ROUND4(AX, BX, CX, DX, 11, 0xf7537e82, 6)
273+
ROUND4(DX, AX, BX, CX, 2, 0xbd3af235, 10)
274+
ROUND4(CX, DX, AX, BX, 9, 0x2ad7d2bb, 15)
275+
ROUND4(BX, CX, DX, AX, -1, 0xeb86d391, 21)
276+
277+
x.ADDL(R12, AX)
278+
x.ADDL(R13, BX)
279+
x.ADDL(R14, CX)
280+
x.ADDL(R15, DX)
281+
282+
// NEXT LOOP
283+
x.Comment("Prepare next loop")
284+
x.ADDQ(o.U8(64), src)
285+
x.CMPQ(src, end)
286+
x.JB(o.LabelRef("loop"))
287+
288+
// Write...
289+
x.Comment("Write output")
290+
digest = x.Load(x.Param("dig"), x.GP64())
291+
for i := range dig {
292+
x.MOVL(dig[i], o.Mem{Base: digest, Disp: i * 4})
293+
}
294+
295+
x.Label("end")
296+
x.RET()
297+
298+
x.Generate()
299+
}

_gen/go.mod

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module github.com/minio/md5-simd/_gen
2+
3+
go 1.14
4+
5+
require github.com/mmcloughlin/avo v0.0.0-20210104032911-599bdd1269f4 // indirect

_gen/go.sum

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
github.com/mmcloughlin/avo v0.0.0-20210104032911-599bdd1269f4 h1:ExoghBBFY7A3RzgkAOq0XbHs9zaT/bHq7xysgyp3z3Q=
2+
github.com/mmcloughlin/avo v0.0.0-20210104032911-599bdd1269f4/go.mod h1:6aKT4zZIrpGqB3RpFU14ByCSSyKY6LfJz4J/JJChHfI=
3+
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
4+
golang.org/x/arch v0.0.0-20201008161808-52c3e6f60cff/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4=
5+
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
6+
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
7+
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
8+
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
9+
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
10+
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
11+
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
12+
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
13+
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
14+
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
15+
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
16+
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
17+
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
18+
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
19+
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
20+
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
21+
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
22+
golang.org/x/tools v0.0.0-20201105001634-bc3cf281b174 h1:0rx0F4EjJNbxTuzWe0KjKcIzs+3VEb/Mrs/d1ciNz1c=
23+
golang.org/x/tools v0.0.0-20201105001634-bc3cf281b174/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
24+
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
25+
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
26+
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
27+
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
28+
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

0 commit comments

Comments
 (0)