OLD | NEW |
(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 } |
OLD | NEW |