Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(267)

Side by Side Diff: src/cmd/vet/asmdecl.go

Issue 7531045: code review 7531045: cmd/vet: add assembly checker (Closed)
Patch Set: diff -r 8ab9556841e4 https://code.google.com/p/go/ Created 12 years ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « src/cmd/vet/Makefile ('k') | src/cmd/vet/buildtag.go » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2013 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 // Identify mismatches between assembly files and Go func declarations.
6
7 package main
8
9 import (
10 "bytes"
11 "fmt"
12 "go/ast"
13 "go/token"
14 "regexp"
15 "strconv"
16 "strings"
17 )
18
19 // 'kind' is a kind of assembly variable.
20 // The kinds 1, 2, 4, 8 stand for values of that size.
21 type asmKind int
22
23 // These special kinds are not valid sizes.
24 const (
25 asmString asmKind = 100 + iota
26 asmSlice
27 asmInterface
28 asmEmptyInterface
29 )
30
31 // An asmArch describes assembly parameters for an architecture
32 type asmArch struct {
33 name string
34 ptrSize int
35 intSize int
36 bigEndian bool
37 }
38
39 // An asmFunc describes the expected variables for a function on a given archite cture.
40 type asmFunc struct {
41 arch *asmArch
42 size int // size of all arguments
43 vars map[string]*asmVar
44 varByOffset map[int]*asmVar
45 }
46
47 // An asmVar describes a single assembly variable.
48 type asmVar struct {
49 name string
50 kind asmKind
51 typ string
52 off int
53 size int
54 inner []*asmVar
55 }
56
57 var (
58 asmArch386 = asmArch{"386", 4, 4, false}
59 asmArchArm = asmArch{"arm", 4, 4, false}
60 asmArchAmd64 = asmArch{"amd64", 8, 8, false}
61
62 arches = []*asmArch{
63 &asmArch386,
64 &asmArchArm,
65 &asmArchAmd64,
66 }
67 )
68
69 var (
70 re = regexp.MustCompile
71 asmPlusBuild = re(`//\s+\+build\s+([^\n]+)`)
72 asmTEXT = re(`\bTEXT\b.*·([^\(]+)\(SB\)(?:\s*,\s*([0-9]+))?(?:\s*,\ s*\$([0-9]+)(?:-([0-9]+))?)?`)
73 asmDATA = re(`\b(DATA|GLOBL)\b`)
74 asmNamedFP = re(`([a-zA-Z0-9_\xFF-\x{10FFFF}]+)(?:\+([0-9]+))\(FP\)`)
75 asmUnnamedFP = re(`[^+\-0-9]](([0-9]+)\(FP\))`)
76 asmOpcode = re(`^\s*(?:[A-Z0-9a-z_]+:)?\s*([A-Z]+)\s*([^,]*)(?:,\s*(. *))?`)
77 )
78
79 func asmCheck(pkg *Package) {
80 if !vet("asmdecl") {
81 return
82 }
83
84 // No work if no assembly files.
85 if !pkg.hasFileWithSuffix(".s") {
86 return
87 }
88
89 // Gather declarations. knownFunc[name][arch] is func description.
90 knownFunc := make(map[string]map[string]*asmFunc)
91
92 for _, f := range pkg.files {
93 if f.file != nil {
94 for _, decl := range f.file.Decls {
95 if decl, ok := decl.(*ast.FuncDecl); ok && decl. Body == nil {
96 knownFunc[decl.Name.Name] = f.asmParseDe cl(decl)
97 }
98 }
99 }
100 }
101
102 var fn *asmFunc
103 for _, f := range pkg.files {
104 if !strings.HasSuffix(f.name, ".s") {
105 continue
106 }
107
108 // Determine architecture from file name if possible.
109 var arch string
110 for _, a := range arches {
111 if strings.HasSuffix(f.name, "_"+a.name+".s") {
112 arch = a.name
113 break
114 }
115 }
116
117 lines := strings.SplitAfter(string(f.content), "\n")
118 for lineno, line := range lines {
119 lineno++
120
121 warnf := func(format string, args ...interface{}) {
122 f.Warnf(token.NoPos, "%s:%d: [%s] %s", f.name, l ineno, arch, fmt.Sprintf(format, args...))
123 }
124
125 if arch == "" {
126 // Determine architecture from +build line if po ssible.
127 if m := asmPlusBuild.FindStringSubmatch(line); m != nil {
128 Fields:
129 for _, fld := range strings.Fields(m[1]) {
130 for _, a := range arches {
131 if a.name == fld {
132 arch = a.name
133 break Fields
134 }
135 }
136 }
137 }
138 }
139
140 if m := asmTEXT.FindStringSubmatch(line); m != nil {
141 if arch == "" {
142 f.Warnf(token.NoPos, "%s: cannot determi ne architecture for assembly file", f.name)
143 return
144 }
145 fn = knownFunc[m[1]][arch]
146 if fn != nil {
147 size, _ := strconv.Atoi(m[4])
148 if size != fn.size && (m[2] != "7" || si ze != 0) {
149 warnf("wrong argument size %d; e xpected $...-%d", size, fn.size)
150 }
151 }
152 continue
153 } else if strings.Contains(line, "TEXT") && strings.Cont ains(line, "SB") {
154 // function, but not visible from Go (didn't mat ch asmTEXT), so stop checking
155 fn = nil
156 continue
157 }
158
159 if asmDATA.FindStringSubmatch(line) != nil {
160 fn = nil
161 }
162 if fn == nil {
163 continue
164 }
165
166 for _, m := range asmUnnamedFP.FindAllStringSubmatch(lin e, -1) {
167 warnf("use of unnamed argument %s", m[1])
168 }
169
170 for _, m := range asmNamedFP.FindAllStringSubmatch(line, -1) {
171 name := m[1]
172 off := 0
173 if m[2] != "" {
174 off, _ = strconv.Atoi(m[2])
175 }
176 v := fn.vars[name]
177 if v == nil {
178 // Allow argframe+0(FP).
179 if name == "argframe" && off == 0 {
180 continue
181 }
182 v = fn.varByOffset[off]
183 if v != nil {
184 warnf("unknown variable %s; offs et %d is %s+%d(FP)", name, off, v.name, v.off)
185 } else {
186 warnf("unknown variable %s", nam e)
187 }
188 continue
189 }
190 asmCheckVar(warnf, fn, line, m[0], off, v)
191 }
192 }
193 }
194 }
195
196 // asmParseDecl parses a function decl for expected assembly variables.
197 func (f *File) asmParseDecl(decl *ast.FuncDecl) map[string]*asmFunc {
198 var (
199 arch *asmArch
200 fn *asmFunc
201 offset int
202 failed bool
203 )
204
205 addVar := func(outer string, v asmVar) {
206 if vo := fn.vars[outer]; vo != nil {
207 vo.inner = append(vo.inner, &v)
208 }
209 fn.vars[v.name] = &v
210 for i := 0; i < v.size; i++ {
211 fn.varByOffset[v.off+i] = &v
212 }
213 }
214
215 addParams := func(list []*ast.Field) {
216 for i, fld := range list {
217 // Determine alignment, size, and kind of type in declar ation.
218 var align, size int
219 var kind asmKind
220 names := fld.Names
221 typ := f.gofmt(fld.Type)
222 switch t := fld.Type.(type) {
223 default:
224 switch typ {
225 default:
226 f.Warnf(fld.Type.Pos(), "unknown assembl y argument type %s", typ)
227 failed = true
228 return
229 case "int8", "uint8", "byte", "bool":
230 size = 1
231 case "int16", "uint16":
232 size = 2
233 case "int32", "uint32", "float32":
234 size = 4
235 case "int64", "uint64", "float64":
236 align = arch.ptrSize
237 size = 8
238 case "int", "uint":
239 size = arch.intSize
240 case "uintptr", "iword", "Word", "Errno", "unsaf e.Pointer":
241 size = arch.ptrSize
242 case "string":
243 size = arch.ptrSize * 2
244 align = arch.ptrSize
245 kind = asmString
246 }
247 case *ast.ChanType, *ast.FuncType, *ast.MapType, *ast.St arExpr:
248 size = arch.ptrSize
249 case *ast.InterfaceType:
250 align = arch.ptrSize
251 size = 2 * arch.ptrSize
252 if len(t.Methods.List) > 0 {
253 kind = asmInterface
254 } else {
255 kind = asmEmptyInterface
256 }
257 case *ast.ArrayType:
258 if t.Len == nil {
259 size = arch.ptrSize + 2*arch.intSize
260 align = arch.ptrSize
261 kind = asmSlice
262 break
263 }
264 f.Warnf(fld.Type.Pos(), "unsupported assembly ar gument type %s", typ)
265 failed = true
266 case *ast.StructType:
267 f.Warnf(fld.Type.Pos(), "unsupported assembly ar gument type %s", typ)
268 failed = true
269 }
270 if align == 0 {
271 align = size
272 }
273 if kind == 0 {
274 kind = asmKind(size)
275 }
276 offset += -offset & (align - 1)
277
278 // Create variable for each name being declared with thi s type.
279 if len(names) == 0 {
280 name := "unnamed"
281 if decl.Type.Results != nil && len(decl.Type.Res ults.List) > 0 && &list[0] == &decl.Type.Results.List[0] && i == 0 {
282 // Assume assembly will refer to single unnamed result as r.
283 name = "ret"
284 }
285 names = []*ast.Ident{{Name: name}}
286 }
287 for _, id := range names {
288 name := id.Name
289 addVar("", asmVar{
290 name: name,
291 kind: kind,
292 typ: typ,
293 off: offset,
294 size: size,
295 })
296 switch kind {
297 case 8:
298 if arch.ptrSize == 4 {
299 w1, w2 := "lo", "hi"
300 if arch.bigEndian {
301 w1, w2 = w2, w1
302 }
303 addVar(name, asmVar{
304 name: name + "_" + w1,
305 kind: 4,
306 typ: "half " + typ,
307 off: offset,
308 size: 4,
309 })
310 addVar(name, asmVar{
311 name: name + "_" + w2,
312 kind: 4,
313 typ: "half " + typ,
314 off: offset + 4,
315 size: 4,
316 })
317 }
318
319 case asmEmptyInterface:
320 addVar(name, asmVar{
321 name: name + "_type",
322 kind: asmKind(arch.ptrSize),
323 typ: "interface type",
324 off: offset,
325 size: arch.ptrSize,
326 })
327 addVar(name, asmVar{
328 name: name + "_data",
329 kind: asmKind(arch.ptrSize),
330 typ: "interface data",
331 off: offset + arch.ptrSize,
332 size: arch.ptrSize,
333 })
334
335 case asmInterface:
336 addVar(name, asmVar{
337 name: name + "_itable",
338 kind: asmKind(arch.ptrSize),
339 typ: "interface itable",
340 off: offset,
341 size: arch.ptrSize,
342 })
343 addVar(name, asmVar{
344 name: name + "_data",
345 kind: asmKind(arch.ptrSize),
346 typ: "interface data",
347 off: offset + arch.ptrSize,
348 size: arch.ptrSize,
349 })
350
351 case asmSlice:
352 addVar(name, asmVar{
353 name: name + "_base",
354 kind: asmKind(arch.ptrSize),
355 typ: "slice base",
356 off: offset,
357 size: arch.ptrSize,
358 })
359 addVar(name, asmVar{
360 name: name + "_len",
361 kind: asmKind(arch.intSize),
362 typ: "slice len",
363 off: offset + arch.ptrSize,
364 size: arch.intSize,
365 })
366 addVar(name, asmVar{
367 name: name + "_cap",
368 kind: asmKind(arch.intSize),
369 typ: "slice cap",
370 off: offset + arch.ptrSize + ar ch.intSize,
371 size: arch.intSize,
372 })
373
374 case asmString:
375 addVar(name, asmVar{
376 name: name + "_base",
377 kind: asmKind(arch.ptrSize),
378 typ: "string base",
379 off: offset,
380 size: arch.ptrSize,
381 })
382 addVar(name, asmVar{
383 name: name + "_len",
384 kind: asmKind(arch.intSize),
385 typ: "string len",
386 off: offset + arch.ptrSize,
387 size: arch.intSize,
388 })
389 }
390 offset += size
391 }
392 }
393 }
394
395 m := make(map[string]*asmFunc)
396 for _, arch = range arches {
397 fn = &asmFunc{
398 arch: arch,
399 vars: make(map[string]*asmVar),
400 varByOffset: make(map[int]*asmVar),
401 }
402 offset = 0
403 addParams(decl.Type.Params.List)
404 if decl.Type.Results != nil && len(decl.Type.Results.List) > 0 {
405 offset += -offset & (arch.ptrSize - 1)
406 addParams(decl.Type.Results.List)
407 }
408 fn.size = offset
409 m[arch.name] = fn
410 }
411
412 if failed {
413 return nil
414 }
415 return m
416 }
417
418 // asmCheckVar checks a single variable reference.
419 func asmCheckVar(warnf func(string, ...interface{}), fn *asmFunc, line, expr str ing, off int, v *asmVar) {
420 m := asmOpcode.FindStringSubmatch(line)
421 if m == nil {
422 warnf("cannot find assembly opcode")
423 }
424
425 // Determine operand sizes from instruction.
426 // Typically the suffix suffices, but there are exceptions.
427 var src, dst, kind asmKind
428 op := m[1]
429 switch fn.arch.name + "." + op {
430 case "386.FMOVLP":
431 src, dst = 8, 4
432 case "arm.MOVD":
433 src = 8
434 case "arm.MOVW":
435 src = 4
436 case "arm.MOVH", "arm.MOVHU":
437 src = 2
438 case "arm.MOVB", "arm.MOVBU":
439 src = 1
440 default:
441 if fn.arch.name == "386" || fn.arch.name == "amd64" {
442 if strings.HasPrefix(op, "F") && (strings.HasSuffix(op, "D") || strings.HasSuffix(op, "DP")) {
443 // FMOVDP, FXCHD, etc
444 src = 8
445 break
446 }
447 if strings.HasPrefix(op, "F") && (strings.HasSuffix(op, "F") || strings.HasSuffix(op, "FP")) {
448 // FMOVFP, FXCHF, etc
449 src = 4
450 break
451 }
452 if strings.HasSuffix(op, "SD") {
453 // MOVSD, SQRTSD, etc
454 src = 8
455 break
456 }
457 if strings.HasSuffix(op, "SS") {
458 // MOVSS, SQRTSS, etc
459 src = 4
460 break
461 }
462 if strings.HasPrefix(op, "SET") {
463 // SETEQ, etc
464 src = 1
465 break
466 }
467 switch op[len(op)-1] {
468 case 'B':
469 src = 1
470 case 'W':
471 src = 2
472 case 'L':
473 src = 4
474 case 'D', 'Q':
475 src = 8
476 }
477 }
478 }
479 if dst == 0 {
480 dst = src
481 }
482
483 // Determine whether the match we're holding
484 // is the first or second argument.
485 if strings.Index(line, expr) > strings.Index(line, ",") {
486 kind = dst
487 } else {
488 kind = src
489 }
490
491 vk := v.kind
492 vt := v.typ
493 switch vk {
494 case asmInterface, asmEmptyInterface, asmString, asmSlice:
495 // allow reference to first word (pointer)
496 vk = v.inner[0].kind
497 vt = v.inner[0].typ
498 }
499
500 if off != v.off {
501 var inner bytes.Buffer
502 for i, vi := range v.inner {
503 if len(v.inner) > 1 {
504 fmt.Fprintf(&inner, ",")
505 }
506 fmt.Fprintf(&inner, " ")
507 if i == len(v.inner)-1 {
508 fmt.Fprintf(&inner, "or ")
509 }
510 fmt.Fprintf(&inner, "%s+%d(FP)", vi.name, vi.off)
511 }
512 warnf("invalid offset %s; expected %s+%d(FP)%s", expr, v.name, v .off, inner.String())
513 return
514 }
515 if kind != 0 && kind != vk {
516 var inner bytes.Buffer
517 if len(v.inner) > 0 {
518 fmt.Fprintf(&inner, " containing")
519 for i, vi := range v.inner {
520 if i > 0 && len(v.inner) > 2 {
521 fmt.Fprintf(&inner, ",")
522 }
523 fmt.Fprintf(&inner, " ")
524 if i > 0 && i == len(v.inner)-1 {
525 fmt.Fprintf(&inner, "and ")
526 }
527 fmt.Fprintf(&inner, "%s+%d(FP)", vi.name, vi.off )
528 }
529 }
530 warnf("invalid %s of %s; %s is %d-byte value%s", op, expr, vt, v k, inner.String())
531 }
532 }
OLDNEW
« no previous file with comments | « src/cmd/vet/Makefile ('k') | src/cmd/vet/buildtag.go » ('j') | no next file with comments »

Powered by Google App Engine
RSS Feeds Recent Issues | This issue
This is Rietveld f62528b