-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathdashs.go
272 lines (257 loc) · 6.17 KB
/
dashs.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
package main
import (
"bufio"
"bytes"
"crypto/sha256"
"fmt"
"hash"
"io"
"log"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
)
func compareFunctions(platform string, before, after commit) {
await, ascan := streamDashS(platform, before)
bwait, bscan := streamDashS(platform, after)
compareFuncReaders(ascan, bscan, before.sha, after.sha)
await()
bwait()
}
func compareFuncReaders(a, b io.Reader, aHash, bHash string) {
sizesBuf := new(bytes.Buffer)
sizes := newFilesizes(sizesBuf)
aChan := make(chan *pkgScanner)
go scanDashS(a, []byte(aHash), aChan)
bChan := make(chan *pkgScanner)
go scanDashS(b, []byte(bHash), bChan)
aPkgs := make(map[string]*pkgScanner)
bPkgs := make(map[string]*pkgScanner)
for {
if aChan == nil && bChan == nil {
// all done!
break
}
var aPkg, bPkg *pkgScanner
select {
case aPkg = <-aChan:
if aPkg == nil {
// aChan is done, disable it
aChan = nil
continue
}
var ok bool
bPkg, ok = bPkgs[aPkg.Name]
if !ok {
// we don't have the bPkg for this one, store and wait for later
aPkgs[aPkg.Name] = aPkg
continue
}
// we're going to process this entry, so delete it
delete(bPkgs, aPkg.Name)
case bPkg = <-bChan:
if bPkg == nil {
// bChan is done, disable it
bChan = nil
continue
}
var ok bool
aPkg, ok = aPkgs[bPkg.Name]
if !ok {
// we don't have the aPkg for this one, store and wait for later
bPkgs[bPkg.Name] = bPkg
continue
}
// we're going to process this entry, so delete it
delete(aPkgs, bPkg.Name)
}
pkg := aPkg.Name
needsHeader := true
printHeader := func() {
if !needsHeader {
return
}
fmt.Printf("\n%s%s%s%s\n", ansiFgYellow, ansiBold, pkg, ansiReset)
needsHeader = false
}
var aTot, bTot int
for name, asf := range aPkg.Funcs {
aTot += asf.textsize
bsf, ok := bPkg.Funcs[name]
if !ok {
if *flagFn != "stats" {
printHeader()
fmt.Println("deleted", cleanFuncName(name))
}
continue
}
delete(bPkg.Funcs, name)
bTot += bsf.textsize
if bytes.Equal(asf.bodyhash, bsf.bodyhash) {
continue
}
// TODO: option to show these
if asf.textsize == bsf.textsize {
if *flagFn == "all" {
printHeader()
fmt.Print(ansiFgBlue)
fmt.Println(name, "changed")
fmt.Print(ansiReset)
}
// TODO: option for this?
// diff.Text("a", "b", asf.body, bsf.body, os.Stdout)
continue
}
color := ""
show := true
if asf.textsize < bsf.textsize {
if *flagFn == "smaller" || *flagFn == "stats" {
show = false
}
color = ansiFgRed
} else {
if *flagFn == "bigger" || *flagFn == "stats" {
show = false
}
color = ansiFgGreen
}
if show {
printHeader()
fmt.Print(color)
pct := 100 * (float64(bsf.textsize)/float64(asf.textsize) - 1)
fmt.Printf("%s %d -> %d (%+0.2f%%)\n", cleanFuncName(name), asf.textsize, bsf.textsize, pct)
fmt.Print(ansiReset)
}
}
for name, bsf := range bPkg.Funcs {
if *flagFn != "stats" {
printHeader()
fmt.Println("inserted", cleanFuncName(name))
}
bTot += bsf.textsize
}
sizes.add(pkg+".s", int64(aTot), int64(bTot))
// TODO: option to print these
// printHeader()
// if aTot == bTot {
// fmt.Print(ansiFgBlue)
// } else if aTot < bTot {
// fmt.Print(ansiFgRed)
// } else {
// fmt.Print(ansiFgGreen)
// }
// // TODO: instead, save totals and print at end
// fmt.Printf("%sTOTAL %d -> %d%s\n", ansiBold, aTot, bTot, ansiReset)
}
sizes.flush("text size")
fmt.Println()
io.Copy(os.Stdout, sizesBuf)
for pkg := range aPkgs {
log.Printf("package %s was deleted", pkg)
}
for pkg := range bPkgs {
log.Printf("package %s was added", pkg)
}
}
func cleanFuncName(name string) string {
name = strings.TrimPrefix(name, `"".`)
name = strings.TrimPrefix(name, `type.`)
return name
}
func scanDashS(r io.Reader, sha []byte, c chan<- *pkgScanner) {
// Lazy: attach a fake package to the end
// to flush out the final package being processed.
rr := io.MultiReader(r, strings.NewReader("\n# EOF\n"))
scan := bufio.NewScanner(rr)
var pkgscan *pkgScanner
for scan.Scan() {
// Look for "# pkgname".
b := scan.Bytes()
if len(b) == 0 {
continue
}
if len(b) >= 2 && b[0] == '#' && b[1] == ' ' {
// Found new package.
// If we were working on a package, flush and emit it.
if pkgscan != nil {
pkgscan.flush()
c <- pkgscan
}
pkgscan = &pkgScanner{
Name: string(b[2:]),
Funcs: make(map[string]stextFunc),
Hash: sha256.New(),
}
continue
}
// Not a new package. Pass the line on to the current WIP package.
if pkgscan != nil {
// TODO: bytes.ReplaceAll allocates; modify in-place instead
b = bytes.ReplaceAll(b, sha, []byte("SHA"))
pkgscan.ProcessLine(b)
}
}
check(scan.Err())
close(c)
}
type pkgScanner struct {
Name string
Funcs map[string]stextFunc
// transient state
Hash hash.Hash
stext string
}
func (s *pkgScanner) ProcessLine(b []byte) {
if b[0] != '\t' {
s.flush()
s.stext = string(b)
return
}
s.Hash.Write(b)
s.Hash.Write([]byte{'\n'})
}
func (s *pkgScanner) flush() {
if s.stext != "" && strings.Contains(s.stext, " STEXT ") {
name, size := extractNameAndSize(s.stext)
s.Funcs[name] = stextFunc{
textsize: size,
bodyhash: s.Hash.Sum(nil),
}
}
s.Hash.Reset()
s.stext = ""
}
type stextFunc struct {
textsize int // length in instructions of the function
bodyhash []byte // hash of -S output for the function
body string
}
func extractNameAndSize(stext string) (string, int) {
i := strings.IndexByte(stext, ' ')
name := stext[:i]
stext = stext[i:]
i = strings.Index(stext, " size=")
stext = stext[i+len(" size="):]
i = strings.Index(stext, " ")
stext = stext[:i]
n, err := strconv.Atoi(stext)
check(err)
return name, n
}
func streamDashS(platform string, c commit) (wait func(), r io.Reader) {
cmdgo := filepath.Join(c.dir, "bin", "go")
cmd := exec.Command(cmdgo, "build", "-gcflags=all=-S -dwarf=false", "std", "cmd")
goos, goarch := parsePlatform(platform)
cmd.Env = append(os.Environ(), "GOOS="+goos, "GOARCH="+goarch)
pipe, err := cmd.StderrPipe()
check(err)
err = cmd.Start()
check(err)
wait = func() {
err := cmd.Wait()
check(err)
}
return wait, pipe
}