github.com/xushiwei/go@v0.0.0-20130601165731-2b9d83f45bc9/src/cmd/vet/asmdecl.go (about) 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 architecture. 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.asmParseDecl(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 Println("Checking file", f.name) 108 109 // Determine architecture from file name if possible. 110 var arch string 111 for _, a := range arches { 112 if strings.HasSuffix(f.name, "_"+a.name+".s") { 113 arch = a.name 114 break 115 } 116 } 117 118 lines := strings.SplitAfter(string(f.content), "\n") 119 for lineno, line := range lines { 120 lineno++ 121 122 warnf := func(format string, args ...interface{}) { 123 f.Warnf(token.NoPos, "%s:%d: [%s] %s", f.name, lineno, arch, fmt.Sprintf(format, args...)) 124 } 125 126 if arch == "" { 127 // Determine architecture from +build line if possible. 128 if m := asmPlusBuild.FindStringSubmatch(line); m != nil { 129 Fields: 130 for _, fld := range strings.Fields(m[1]) { 131 for _, a := range arches { 132 if a.name == fld { 133 arch = a.name 134 break Fields 135 } 136 } 137 } 138 } 139 } 140 141 if m := asmTEXT.FindStringSubmatch(line); m != nil { 142 if arch == "" { 143 f.Warnf(token.NoPos, "%s: cannot determine architecture for assembly file", f.name) 144 return 145 } 146 fn = knownFunc[m[1]][arch] 147 if fn != nil { 148 size, _ := strconv.Atoi(m[4]) 149 if size != fn.size && (m[2] != "7" || size != 0) { 150 warnf("wrong argument size %d; expected $...-%d", size, fn.size) 151 } 152 } 153 continue 154 } else if strings.Contains(line, "TEXT") && strings.Contains(line, "SB") { 155 // function, but not visible from Go (didn't match asmTEXT), so stop checking 156 fn = nil 157 continue 158 } 159 160 if asmDATA.FindStringSubmatch(line) != nil { 161 fn = nil 162 } 163 if fn == nil { 164 continue 165 } 166 167 for _, m := range asmUnnamedFP.FindAllStringSubmatch(line, -1) { 168 warnf("use of unnamed argument %s", m[1]) 169 } 170 171 for _, m := range asmNamedFP.FindAllStringSubmatch(line, -1) { 172 name := m[1] 173 off := 0 174 if m[2] != "" { 175 off, _ = strconv.Atoi(m[2]) 176 } 177 v := fn.vars[name] 178 if v == nil { 179 // Allow argframe+0(FP). 180 if name == "argframe" && off == 0 { 181 continue 182 } 183 v = fn.varByOffset[off] 184 if v != nil { 185 warnf("unknown variable %s; offset %d is %s+%d(FP)", name, off, v.name, v.off) 186 } else { 187 warnf("unknown variable %s", name) 188 } 189 continue 190 } 191 asmCheckVar(warnf, fn, line, m[0], off, v) 192 } 193 } 194 } 195 } 196 197 // asmParseDecl parses a function decl for expected assembly variables. 198 func (f *File) asmParseDecl(decl *ast.FuncDecl) map[string]*asmFunc { 199 var ( 200 arch *asmArch 201 fn *asmFunc 202 offset int 203 failed bool 204 ) 205 206 addVar := func(outer string, v asmVar) { 207 if vo := fn.vars[outer]; vo != nil { 208 vo.inner = append(vo.inner, &v) 209 } 210 fn.vars[v.name] = &v 211 for i := 0; i < v.size; i++ { 212 fn.varByOffset[v.off+i] = &v 213 } 214 } 215 216 addParams := func(list []*ast.Field) { 217 for i, fld := range list { 218 // Determine alignment, size, and kind of type in declaration. 219 var align, size int 220 var kind asmKind 221 names := fld.Names 222 typ := f.gofmt(fld.Type) 223 switch t := fld.Type.(type) { 224 default: 225 switch typ { 226 default: 227 f.Warnf(fld.Type.Pos(), "unknown assembly argument type %s", typ) 228 failed = true 229 return 230 case "int8", "uint8", "byte", "bool": 231 size = 1 232 case "int16", "uint16": 233 size = 2 234 case "int32", "uint32", "float32": 235 size = 4 236 case "int64", "uint64", "float64": 237 align = arch.ptrSize 238 size = 8 239 case "int", "uint": 240 size = arch.intSize 241 case "uintptr", "iword", "Word", "Errno", "unsafe.Pointer": 242 size = arch.ptrSize 243 case "string": 244 size = arch.ptrSize * 2 245 align = arch.ptrSize 246 kind = asmString 247 } 248 case *ast.ChanType, *ast.FuncType, *ast.MapType, *ast.StarExpr: 249 size = arch.ptrSize 250 case *ast.InterfaceType: 251 align = arch.ptrSize 252 size = 2 * arch.ptrSize 253 if len(t.Methods.List) > 0 { 254 kind = asmInterface 255 } else { 256 kind = asmEmptyInterface 257 } 258 case *ast.ArrayType: 259 if t.Len == nil { 260 size = arch.ptrSize + 2*arch.intSize 261 align = arch.ptrSize 262 kind = asmSlice 263 break 264 } 265 f.Warnf(fld.Type.Pos(), "unsupported assembly argument type %s", typ) 266 failed = true 267 case *ast.StructType: 268 f.Warnf(fld.Type.Pos(), "unsupported assembly argument type %s", typ) 269 failed = true 270 } 271 if align == 0 { 272 align = size 273 } 274 if kind == 0 { 275 kind = asmKind(size) 276 } 277 offset += -offset & (align - 1) 278 279 // Create variable for each name being declared with this type. 280 if len(names) == 0 { 281 name := "unnamed" 282 if decl.Type.Results != nil && len(decl.Type.Results.List) > 0 && &list[0] == &decl.Type.Results.List[0] && i == 0 { 283 // Assume assembly will refer to single unnamed result as r. 284 name = "ret" 285 } 286 names = []*ast.Ident{{Name: name}} 287 } 288 for _, id := range names { 289 name := id.Name 290 addVar("", asmVar{ 291 name: name, 292 kind: kind, 293 typ: typ, 294 off: offset, 295 size: size, 296 }) 297 switch kind { 298 case 8: 299 if arch.ptrSize == 4 { 300 w1, w2 := "lo", "hi" 301 if arch.bigEndian { 302 w1, w2 = w2, w1 303 } 304 addVar(name, asmVar{ 305 name: name + "_" + w1, 306 kind: 4, 307 typ: "half " + typ, 308 off: offset, 309 size: 4, 310 }) 311 addVar(name, asmVar{ 312 name: name + "_" + w2, 313 kind: 4, 314 typ: "half " + typ, 315 off: offset + 4, 316 size: 4, 317 }) 318 } 319 320 case asmEmptyInterface: 321 addVar(name, asmVar{ 322 name: name + "_type", 323 kind: asmKind(arch.ptrSize), 324 typ: "interface type", 325 off: offset, 326 size: arch.ptrSize, 327 }) 328 addVar(name, asmVar{ 329 name: name + "_data", 330 kind: asmKind(arch.ptrSize), 331 typ: "interface data", 332 off: offset + arch.ptrSize, 333 size: arch.ptrSize, 334 }) 335 336 case asmInterface: 337 addVar(name, asmVar{ 338 name: name + "_itable", 339 kind: asmKind(arch.ptrSize), 340 typ: "interface itable", 341 off: offset, 342 size: arch.ptrSize, 343 }) 344 addVar(name, asmVar{ 345 name: name + "_data", 346 kind: asmKind(arch.ptrSize), 347 typ: "interface data", 348 off: offset + arch.ptrSize, 349 size: arch.ptrSize, 350 }) 351 352 case asmSlice: 353 addVar(name, asmVar{ 354 name: name + "_base", 355 kind: asmKind(arch.ptrSize), 356 typ: "slice base", 357 off: offset, 358 size: arch.ptrSize, 359 }) 360 addVar(name, asmVar{ 361 name: name + "_len", 362 kind: asmKind(arch.intSize), 363 typ: "slice len", 364 off: offset + arch.ptrSize, 365 size: arch.intSize, 366 }) 367 addVar(name, asmVar{ 368 name: name + "_cap", 369 kind: asmKind(arch.intSize), 370 typ: "slice cap", 371 off: offset + arch.ptrSize + arch.intSize, 372 size: arch.intSize, 373 }) 374 375 case asmString: 376 addVar(name, asmVar{ 377 name: name + "_base", 378 kind: asmKind(arch.ptrSize), 379 typ: "string base", 380 off: offset, 381 size: arch.ptrSize, 382 }) 383 addVar(name, asmVar{ 384 name: name + "_len", 385 kind: asmKind(arch.intSize), 386 typ: "string len", 387 off: offset + arch.ptrSize, 388 size: arch.intSize, 389 }) 390 } 391 offset += size 392 } 393 } 394 } 395 396 m := make(map[string]*asmFunc) 397 for _, arch = range arches { 398 fn = &asmFunc{ 399 arch: arch, 400 vars: make(map[string]*asmVar), 401 varByOffset: make(map[int]*asmVar), 402 } 403 offset = 0 404 addParams(decl.Type.Params.List) 405 if decl.Type.Results != nil && len(decl.Type.Results.List) > 0 { 406 offset += -offset & (arch.ptrSize - 1) 407 addParams(decl.Type.Results.List) 408 } 409 fn.size = offset 410 m[arch.name] = fn 411 } 412 413 if failed { 414 return nil 415 } 416 return m 417 } 418 419 // asmCheckVar checks a single variable reference. 420 func asmCheckVar(warnf func(string, ...interface{}), fn *asmFunc, line, expr string, off int, v *asmVar) { 421 m := asmOpcode.FindStringSubmatch(line) 422 if m == nil { 423 warnf("cannot find assembly opcode") 424 } 425 426 // Determine operand sizes from instruction. 427 // Typically the suffix suffices, but there are exceptions. 428 var src, dst, kind asmKind 429 op := m[1] 430 switch fn.arch.name + "." + op { 431 case "386.FMOVLP": 432 src, dst = 8, 4 433 case "arm.MOVD": 434 src = 8 435 case "arm.MOVW": 436 src = 4 437 case "arm.MOVH", "arm.MOVHU": 438 src = 2 439 case "arm.MOVB", "arm.MOVBU": 440 src = 1 441 default: 442 if fn.arch.name == "386" || fn.arch.name == "amd64" { 443 if strings.HasPrefix(op, "F") && (strings.HasSuffix(op, "D") || strings.HasSuffix(op, "DP")) { 444 // FMOVDP, FXCHD, etc 445 src = 8 446 break 447 } 448 if strings.HasPrefix(op, "F") && (strings.HasSuffix(op, "F") || strings.HasSuffix(op, "FP")) { 449 // FMOVFP, FXCHF, etc 450 src = 4 451 break 452 } 453 if strings.HasSuffix(op, "SD") { 454 // MOVSD, SQRTSD, etc 455 src = 8 456 break 457 } 458 if strings.HasSuffix(op, "SS") { 459 // MOVSS, SQRTSS, etc 460 src = 4 461 break 462 } 463 if strings.HasPrefix(op, "SET") { 464 // SETEQ, etc 465 src = 1 466 break 467 } 468 switch op[len(op)-1] { 469 case 'B': 470 src = 1 471 case 'W': 472 src = 2 473 case 'L': 474 src = 4 475 case 'D', 'Q': 476 src = 8 477 } 478 } 479 } 480 if dst == 0 { 481 dst = src 482 } 483 484 // Determine whether the match we're holding 485 // is the first or second argument. 486 if strings.Index(line, expr) > strings.Index(line, ",") { 487 kind = dst 488 } else { 489 kind = src 490 } 491 492 vk := v.kind 493 vt := v.typ 494 switch vk { 495 case asmInterface, asmEmptyInterface, asmString, asmSlice: 496 // allow reference to first word (pointer) 497 vk = v.inner[0].kind 498 vt = v.inner[0].typ 499 } 500 501 if off != v.off { 502 var inner bytes.Buffer 503 for i, vi := range v.inner { 504 if len(v.inner) > 1 { 505 fmt.Fprintf(&inner, ",") 506 } 507 fmt.Fprintf(&inner, " ") 508 if i == len(v.inner)-1 { 509 fmt.Fprintf(&inner, "or ") 510 } 511 fmt.Fprintf(&inner, "%s+%d(FP)", vi.name, vi.off) 512 } 513 warnf("invalid offset %s; expected %s+%d(FP)%s", expr, v.name, v.off, inner.String()) 514 return 515 } 516 if kind != 0 && kind != vk { 517 var inner bytes.Buffer 518 if len(v.inner) > 0 { 519 fmt.Fprintf(&inner, " containing") 520 for i, vi := range v.inner { 521 if i > 0 && len(v.inner) > 2 { 522 fmt.Fprintf(&inner, ",") 523 } 524 fmt.Fprintf(&inner, " ") 525 if i > 0 && i == len(v.inner)-1 { 526 fmt.Fprintf(&inner, "and ") 527 } 528 fmt.Fprintf(&inner, "%s+%d(FP)", vi.name, vi.off) 529 } 530 } 531 warnf("invalid %s of %s; %s is %d-byte value%s", op, expr, vt, vk, inner.String()) 532 } 533 }