github.com/euank/go@v0.0.0-20160829210321-495514729181/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/build" 14 "go/token" 15 "go/types" 16 "regexp" 17 "strconv" 18 "strings" 19 ) 20 21 // 'kind' is a kind of assembly variable. 22 // The kinds 1, 2, 4, 8 stand for values of that size. 23 type asmKind int 24 25 // These special kinds are not valid sizes. 26 const ( 27 asmString asmKind = 100 + iota 28 asmSlice 29 asmArray 30 asmInterface 31 asmEmptyInterface 32 asmStruct 33 asmComplex 34 ) 35 36 // An asmArch describes assembly parameters for an architecture 37 type asmArch struct { 38 name string 39 sizes *types.StdSizes 40 bigEndian bool 41 stack string 42 lr bool 43 } 44 45 // An asmFunc describes the expected variables for a function on a given architecture. 46 type asmFunc struct { 47 arch *asmArch 48 size int // size of all arguments 49 vars map[string]*asmVar 50 varByOffset map[int]*asmVar 51 } 52 53 // An asmVar describes a single assembly variable. 54 type asmVar struct { 55 name string 56 kind asmKind 57 typ string 58 off int 59 size int 60 inner []*asmVar 61 } 62 63 // Common architecture word sizes and alignments. 64 var ( 65 size44 = &types.StdSizes{WordSize: 4, MaxAlign: 4} 66 size48 = &types.StdSizes{WordSize: 4, MaxAlign: 8} 67 size88 = &types.StdSizes{WordSize: 8, MaxAlign: 8} 68 ) 69 70 var ( 71 asmArch386 = asmArch{"386", size44, false, "SP", false} 72 asmArchArm = asmArch{"arm", size44, false, "R13", true} 73 asmArchArm64 = asmArch{"arm64", size88, false, "RSP", true} 74 asmArchAmd64 = asmArch{"amd64", size88, false, "SP", false} 75 asmArchAmd64p32 = asmArch{"amd64p32", size48, false, "SP", false} 76 asmArchMips64 = asmArch{"mips64", size88, true, "R29", true} 77 asmArchMips64LE = asmArch{"mips64le", size88, false, "R29", true} 78 asmArchPpc64 = asmArch{"ppc64", size88, true, "R1", true} 79 asmArchPpc64LE = asmArch{"ppc64le", size88, false, "R1", true} 80 81 arches = []*asmArch{ 82 &asmArch386, 83 &asmArchArm, 84 &asmArchArm64, 85 &asmArchAmd64, 86 &asmArchAmd64p32, 87 &asmArchMips64, 88 &asmArchMips64LE, 89 &asmArchPpc64, 90 &asmArchPpc64LE, 91 } 92 ) 93 94 func (a *asmArch) intSize() int { return int(a.sizes.WordSize) } 95 func (a *asmArch) ptrSize() int { return int(a.sizes.WordSize) } 96 func (a *asmArch) maxAlign() int { return int(a.sizes.MaxAlign) } 97 98 var ( 99 re = regexp.MustCompile 100 asmPlusBuild = re(`//\s+\+build\s+([^\n]+)`) 101 asmTEXT = re(`\bTEXT\b(.*)·([^\(]+)\(SB\)(?:\s*,\s*([0-9A-Z|+]+))?(?:\s*,\s*\$(-?[0-9]+)(?:-([0-9]+))?)?`) 102 asmDATA = re(`\b(DATA|GLOBL)\b`) 103 asmNamedFP = re(`([a-zA-Z0-9_\xFF-\x{10FFFF}]+)(?:\+([0-9]+))\(FP\)`) 104 asmUnnamedFP = re(`[^+\-0-9](([0-9]+)\(FP\))`) 105 asmSP = re(`[^+\-0-9](([0-9]+)\(([A-Z0-9]+)\))`) 106 asmOpcode = re(`^\s*(?:[A-Z0-9a-z_]+:)?\s*([A-Z]+)\s*([^,]*)(?:,\s*(.*))?`) 107 ppc64Suff = re(`([BHWD])(ZU|Z|U|BR)?$`) 108 ) 109 110 func asmCheck(pkg *Package) { 111 if !vet("asmdecl") { 112 return 113 } 114 115 // No work if no assembly files. 116 if !pkg.hasFileWithSuffix(".s") { 117 return 118 } 119 120 // Gather declarations. knownFunc[name][arch] is func description. 121 knownFunc := make(map[string]map[string]*asmFunc) 122 123 for _, f := range pkg.files { 124 if f.file != nil { 125 for _, decl := range f.file.Decls { 126 if decl, ok := decl.(*ast.FuncDecl); ok && decl.Body == nil { 127 knownFunc[decl.Name.Name] = f.asmParseDecl(decl) 128 } 129 } 130 } 131 } 132 133 Files: 134 for _, f := range pkg.files { 135 if !strings.HasSuffix(f.name, ".s") { 136 continue 137 } 138 Println("Checking file", f.name) 139 140 // Determine architecture from file name if possible. 141 var arch string 142 var archDef *asmArch 143 for _, a := range arches { 144 if strings.HasSuffix(f.name, "_"+a.name+".s") { 145 arch = a.name 146 archDef = a 147 break 148 } 149 } 150 151 lines := strings.SplitAfter(string(f.content), "\n") 152 var ( 153 fn *asmFunc 154 fnName string 155 localSize, argSize int 156 wroteSP bool 157 haveRetArg bool 158 retLine []int 159 ) 160 161 flushRet := func() { 162 if fn != nil && fn.vars["ret"] != nil && !haveRetArg && len(retLine) > 0 { 163 v := fn.vars["ret"] 164 for _, line := range retLine { 165 f.Badf(token.NoPos, "%s:%d: [%s] %s: RET without writing to %d-byte ret+%d(FP)", f.name, line, arch, fnName, v.size, v.off) 166 } 167 } 168 retLine = nil 169 } 170 for lineno, line := range lines { 171 lineno++ 172 173 badf := func(format string, args ...interface{}) { 174 f.Badf(token.NoPos, "%s:%d: [%s] %s: %s", f.name, lineno, arch, fnName, fmt.Sprintf(format, args...)) 175 } 176 177 if arch == "" { 178 // Determine architecture from +build line if possible. 179 if m := asmPlusBuild.FindStringSubmatch(line); m != nil { 180 // There can be multiple architectures in a single +build line, 181 // so accumulate them all and then prefer the one that 182 // matches build.Default.GOARCH. 183 var archCandidates []*asmArch 184 for _, fld := range strings.Fields(m[1]) { 185 for _, a := range arches { 186 if a.name == fld { 187 archCandidates = append(archCandidates, a) 188 } 189 } 190 } 191 for _, a := range archCandidates { 192 if a.name == build.Default.GOARCH { 193 archCandidates = []*asmArch{a} 194 break 195 } 196 } 197 if len(archCandidates) > 0 { 198 arch = archCandidates[0].name 199 archDef = archCandidates[0] 200 } 201 } 202 } 203 204 if m := asmTEXT.FindStringSubmatch(line); m != nil { 205 flushRet() 206 if arch == "" { 207 // Arch not specified by filename or build tags. 208 // Fall back to build.Default.GOARCH. 209 for _, a := range arches { 210 if a.name == build.Default.GOARCH { 211 arch = a.name 212 archDef = a 213 break 214 } 215 } 216 if arch == "" { 217 f.Warnf(token.NoPos, "%s: cannot determine architecture for assembly file", f.name) 218 continue Files 219 } 220 } 221 fnName = m[2] 222 if pkgName := strings.TrimSpace(m[1]); pkgName != "" { 223 pathParts := strings.Split(pkgName, "∕") 224 pkgName = pathParts[len(pathParts)-1] 225 if pkgName != f.pkg.path { 226 f.Warnf(token.NoPos, "%s:%d: [%s] cannot check cross-package assembly function: %s is in package %s", f.name, lineno, arch, fnName, pkgName) 227 fn = nil 228 fnName = "" 229 continue 230 } 231 } 232 fn = knownFunc[fnName][arch] 233 if fn != nil { 234 size, _ := strconv.Atoi(m[5]) 235 flag := m[3] 236 if size != fn.size && (flag != "7" && !strings.Contains(flag, "NOSPLIT") || size != 0) { 237 badf("wrong argument size %d; expected $...-%d", size, fn.size) 238 } 239 } 240 localSize, _ = strconv.Atoi(m[4]) 241 localSize += archDef.intSize() 242 if archDef.lr { 243 // Account for caller's saved LR 244 localSize += archDef.intSize() 245 } 246 argSize, _ = strconv.Atoi(m[5]) 247 if fn == nil && !strings.Contains(fnName, "<>") { 248 badf("function %s missing Go declaration", fnName) 249 } 250 wroteSP = false 251 haveRetArg = false 252 continue 253 } else if strings.Contains(line, "TEXT") && strings.Contains(line, "SB") { 254 // function, but not visible from Go (didn't match asmTEXT), so stop checking 255 flushRet() 256 fn = nil 257 fnName = "" 258 continue 259 } 260 261 if strings.Contains(line, "RET") { 262 retLine = append(retLine, lineno) 263 } 264 265 if fnName == "" { 266 continue 267 } 268 269 if asmDATA.FindStringSubmatch(line) != nil { 270 fn = nil 271 } 272 273 if archDef == nil { 274 continue 275 } 276 277 if strings.Contains(line, ", "+archDef.stack) || strings.Contains(line, ",\t"+archDef.stack) { 278 wroteSP = true 279 continue 280 } 281 282 for _, m := range asmSP.FindAllStringSubmatch(line, -1) { 283 if m[3] != archDef.stack || wroteSP { 284 continue 285 } 286 off := 0 287 if m[1] != "" { 288 off, _ = strconv.Atoi(m[2]) 289 } 290 if off >= localSize { 291 if fn != nil { 292 v := fn.varByOffset[off-localSize] 293 if v != nil { 294 badf("%s should be %s+%d(FP)", m[1], v.name, off-localSize) 295 continue 296 } 297 } 298 if off >= localSize+argSize { 299 badf("use of %s points beyond argument frame", m[1]) 300 continue 301 } 302 badf("use of %s to access argument frame", m[1]) 303 } 304 } 305 306 if fn == nil { 307 continue 308 } 309 310 for _, m := range asmUnnamedFP.FindAllStringSubmatch(line, -1) { 311 off, _ := strconv.Atoi(m[2]) 312 v := fn.varByOffset[off] 313 if v != nil { 314 badf("use of unnamed argument %s; offset %d is %s+%d(FP)", m[1], off, v.name, v.off) 315 } else { 316 badf("use of unnamed argument %s", m[1]) 317 } 318 } 319 320 for _, m := range asmNamedFP.FindAllStringSubmatch(line, -1) { 321 name := m[1] 322 off := 0 323 if m[2] != "" { 324 off, _ = strconv.Atoi(m[2]) 325 } 326 if name == "ret" || strings.HasPrefix(name, "ret_") { 327 haveRetArg = true 328 } 329 v := fn.vars[name] 330 if v == nil { 331 // Allow argframe+0(FP). 332 if name == "argframe" && off == 0 { 333 continue 334 } 335 v = fn.varByOffset[off] 336 if v != nil { 337 badf("unknown variable %s; offset %d is %s+%d(FP)", name, off, v.name, v.off) 338 } else { 339 badf("unknown variable %s", name) 340 } 341 continue 342 } 343 asmCheckVar(badf, fn, line, m[0], off, v) 344 } 345 } 346 flushRet() 347 } 348 } 349 350 func asmKindForType(t types.Type, size int) asmKind { 351 switch t := t.Underlying().(type) { 352 case *types.Basic: 353 switch t.Kind() { 354 case types.String: 355 return asmString 356 case types.Complex64, types.Complex128: 357 return asmComplex 358 } 359 return asmKind(size) 360 case *types.Pointer, *types.Chan, *types.Map, *types.Signature: 361 return asmKind(size) 362 case *types.Struct: 363 return asmStruct 364 case *types.Interface: 365 if t.Empty() { 366 return asmEmptyInterface 367 } 368 return asmInterface 369 case *types.Array: 370 return asmArray 371 case *types.Slice: 372 return asmSlice 373 } 374 panic("unreachable") 375 } 376 377 // A component is an assembly-addressable component of a composite type, 378 // or a composite type itself. 379 type component struct { 380 size int 381 offset int 382 kind asmKind 383 typ string 384 suffix string // Such as _base for string base, _0_lo for lo half of first element of [1]uint64 on 32 bit machine. 385 outer string // The suffix for immediately containing composite type. 386 } 387 388 func newComponent(suffix string, kind asmKind, typ string, offset, size int, outer string) component { 389 return component{suffix: suffix, kind: kind, typ: typ, offset: offset, size: size, outer: outer} 390 } 391 392 // componentsOfType generates a list of components of type t. 393 // For example, given string, the components are the string itself, the base, and the length. 394 func componentsOfType(arch *asmArch, t types.Type) []component { 395 return appendComponentsRecursive(arch, t, nil, "", 0) 396 } 397 398 // appendComponentsRecursive implements componentsOfType. 399 // Recursion is required to correct handle structs and arrays, 400 // which can contain arbitrary other types. 401 func appendComponentsRecursive(arch *asmArch, t types.Type, cc []component, suffix string, off int) []component { 402 s := t.String() 403 size := int(arch.sizes.Sizeof(t)) 404 kind := asmKindForType(t, size) 405 cc = append(cc, newComponent(suffix, kind, s, off, size, suffix)) 406 407 switch kind { 408 case 8: 409 if arch.ptrSize() == 4 { 410 w1, w2 := "lo", "hi" 411 if arch.bigEndian { 412 w1, w2 = w2, w1 413 } 414 cc = append(cc, newComponent(suffix+"_"+w1, 4, "half "+s, off, 4, suffix)) 415 cc = append(cc, newComponent(suffix+"_"+w2, 4, "half "+s, off+4, 4, suffix)) 416 } 417 418 case asmEmptyInterface: 419 cc = append(cc, newComponent(suffix+"_type", asmKind(arch.ptrSize()), "interface type", off, arch.ptrSize(), suffix)) 420 cc = append(cc, newComponent(suffix+"_data", asmKind(arch.ptrSize()), "interface data", off+arch.ptrSize(), arch.ptrSize(), suffix)) 421 422 case asmInterface: 423 cc = append(cc, newComponent(suffix+"_itable", asmKind(arch.ptrSize()), "interface itable", off, arch.ptrSize(), suffix)) 424 cc = append(cc, newComponent(suffix+"_data", asmKind(arch.ptrSize()), "interface data", off+arch.ptrSize(), arch.ptrSize(), suffix)) 425 426 case asmSlice: 427 cc = append(cc, newComponent(suffix+"_base", asmKind(arch.ptrSize()), "slice base", off, arch.ptrSize(), suffix)) 428 cc = append(cc, newComponent(suffix+"_len", asmKind(arch.intSize()), "slice len", off+arch.ptrSize(), arch.intSize(), suffix)) 429 cc = append(cc, newComponent(suffix+"_cap", asmKind(arch.intSize()), "slice cap", off+arch.ptrSize()+arch.intSize(), arch.intSize(), suffix)) 430 431 case asmString: 432 cc = append(cc, newComponent(suffix+"_base", asmKind(arch.ptrSize()), "string base", off, arch.ptrSize(), suffix)) 433 cc = append(cc, newComponent(suffix+"_len", asmKind(arch.intSize()), "string len", off+arch.ptrSize(), arch.intSize(), suffix)) 434 435 case asmComplex: 436 fsize := size / 2 437 cc = append(cc, newComponent(suffix+"_real", asmKind(fsize), fmt.Sprintf("real(complex%d)", size*8), off, fsize, suffix)) 438 cc = append(cc, newComponent(suffix+"_imag", asmKind(fsize), fmt.Sprintf("imag(complex%d)", size*8), off+fsize, fsize, suffix)) 439 440 case asmStruct: 441 tu := t.Underlying().(*types.Struct) 442 fields := make([]*types.Var, tu.NumFields()) 443 for i := 0; i < tu.NumFields(); i++ { 444 fields[i] = tu.Field(i) 445 } 446 offsets := arch.sizes.Offsetsof(fields) 447 for i, f := range fields { 448 cc = appendComponentsRecursive(arch, f.Type(), cc, suffix+"_"+f.Name(), off+int(offsets[i])) 449 } 450 451 case asmArray: 452 tu := t.Underlying().(*types.Array) 453 elem := tu.Elem() 454 // Calculate offset of each element array. 455 fields := []*types.Var{ 456 types.NewVar(token.NoPos, nil, "fake0", elem), 457 types.NewVar(token.NoPos, nil, "fake1", elem), 458 } 459 offsets := arch.sizes.Offsetsof(fields) 460 elemoff := int(offsets[1]) 461 for i := 0; i < int(tu.Len()); i++ { 462 cc = appendComponentsRecursive(arch, elem, cc, suffix+"_"+strconv.Itoa(i), i*elemoff) 463 } 464 } 465 466 return cc 467 } 468 469 // asmParseDecl parses a function decl for expected assembly variables. 470 func (f *File) asmParseDecl(decl *ast.FuncDecl) map[string]*asmFunc { 471 var ( 472 arch *asmArch 473 fn *asmFunc 474 offset int 475 ) 476 477 // addParams adds asmVars for each of the parameters in list. 478 // isret indicates whether the list are the arguments or the return values. 479 addParams := func(list []*ast.Field, isret bool) { 480 argnum := 0 481 for _, fld := range list { 482 t := f.pkg.types[fld.Type].Type 483 align := int(arch.sizes.Alignof(t)) 484 size := int(arch.sizes.Sizeof(t)) 485 offset += -offset & (align - 1) 486 cc := componentsOfType(arch, t) 487 488 // names is the list of names with this type. 489 names := fld.Names 490 if len(names) == 0 { 491 // Anonymous args will be called arg, arg1, arg2, ... 492 // Similarly so for return values: ret, ret1, ret2, ... 493 name := "arg" 494 if isret { 495 name = "ret" 496 } 497 if argnum > 0 { 498 name += strconv.Itoa(argnum) 499 } 500 names = []*ast.Ident{ast.NewIdent(name)} 501 } 502 argnum += len(names) 503 504 // Create variable for each name. 505 for _, id := range names { 506 name := id.Name 507 for _, c := range cc { 508 outer := name + c.outer 509 v := asmVar{ 510 name: name + c.suffix, 511 kind: c.kind, 512 typ: c.typ, 513 off: offset + c.offset, 514 size: c.size, 515 } 516 if vo := fn.vars[outer]; vo != nil { 517 vo.inner = append(vo.inner, &v) 518 } 519 fn.vars[v.name] = &v 520 for i := 0; i < v.size; i++ { 521 fn.varByOffset[v.off+i] = &v 522 } 523 } 524 offset += size 525 } 526 } 527 } 528 529 m := make(map[string]*asmFunc) 530 for _, arch = range arches { 531 fn = &asmFunc{ 532 arch: arch, 533 vars: make(map[string]*asmVar), 534 varByOffset: make(map[int]*asmVar), 535 } 536 offset = 0 537 addParams(decl.Type.Params.List, false) 538 if decl.Type.Results != nil && len(decl.Type.Results.List) > 0 { 539 offset += -offset & (arch.maxAlign() - 1) 540 addParams(decl.Type.Results.List, true) 541 } 542 fn.size = offset 543 m[arch.name] = fn 544 } 545 546 return m 547 } 548 549 // asmCheckVar checks a single variable reference. 550 func asmCheckVar(badf func(string, ...interface{}), fn *asmFunc, line, expr string, off int, v *asmVar) { 551 m := asmOpcode.FindStringSubmatch(line) 552 if m == nil { 553 if !strings.HasPrefix(strings.TrimSpace(line), "//") { 554 badf("cannot find assembly opcode") 555 } 556 return 557 } 558 559 // Determine operand sizes from instruction. 560 // Typically the suffix suffices, but there are exceptions. 561 var src, dst, kind asmKind 562 op := m[1] 563 switch fn.arch.name + "." + op { 564 case "386.FMOVLP": 565 src, dst = 8, 4 566 case "arm.MOVD": 567 src = 8 568 case "arm.MOVW": 569 src = 4 570 case "arm.MOVH", "arm.MOVHU": 571 src = 2 572 case "arm.MOVB", "arm.MOVBU": 573 src = 1 574 // LEA* opcodes don't really read the second arg. 575 // They just take the address of it. 576 case "386.LEAL": 577 dst = 4 578 case "amd64.LEAQ": 579 dst = 8 580 case "amd64p32.LEAL": 581 dst = 4 582 default: 583 switch fn.arch.name { 584 case "386", "amd64": 585 if strings.HasPrefix(op, "F") && (strings.HasSuffix(op, "D") || strings.HasSuffix(op, "DP")) { 586 // FMOVDP, FXCHD, etc 587 src = 8 588 break 589 } 590 if strings.HasPrefix(op, "P") && strings.HasSuffix(op, "RD") { 591 // PINSRD, PEXTRD, etc 592 src = 4 593 break 594 } 595 if strings.HasPrefix(op, "F") && (strings.HasSuffix(op, "F") || strings.HasSuffix(op, "FP")) { 596 // FMOVFP, FXCHF, etc 597 src = 4 598 break 599 } 600 if strings.HasSuffix(op, "SD") { 601 // MOVSD, SQRTSD, etc 602 src = 8 603 break 604 } 605 if strings.HasSuffix(op, "SS") { 606 // MOVSS, SQRTSS, etc 607 src = 4 608 break 609 } 610 if strings.HasPrefix(op, "SET") { 611 // SETEQ, etc 612 src = 1 613 break 614 } 615 switch op[len(op)-1] { 616 case 'B': 617 src = 1 618 case 'W': 619 src = 2 620 case 'L': 621 src = 4 622 case 'D', 'Q': 623 src = 8 624 } 625 case "ppc64", "ppc64le": 626 // Strip standard suffixes to reveal size letter. 627 m := ppc64Suff.FindStringSubmatch(op) 628 if m != nil { 629 switch m[1][0] { 630 case 'B': 631 src = 1 632 case 'H': 633 src = 2 634 case 'W': 635 src = 4 636 case 'D': 637 src = 8 638 } 639 } 640 case "mips64", "mips64le": 641 switch op { 642 case "MOVB", "MOVBU": 643 src = 1 644 case "MOVH", "MOVHU": 645 src = 2 646 case "MOVW", "MOVWU", "MOVF": 647 src = 4 648 case "MOVV", "MOVD": 649 src = 8 650 } 651 } 652 } 653 if dst == 0 { 654 dst = src 655 } 656 657 // Determine whether the match we're holding 658 // is the first or second argument. 659 if strings.Index(line, expr) > strings.Index(line, ",") { 660 kind = dst 661 } else { 662 kind = src 663 } 664 665 vk := v.kind 666 vs := v.size 667 vt := v.typ 668 switch vk { 669 case asmInterface, asmEmptyInterface, asmString, asmSlice: 670 // allow reference to first word (pointer) 671 vk = v.inner[0].kind 672 vs = v.inner[0].size 673 vt = v.inner[0].typ 674 } 675 676 if off != v.off { 677 var inner bytes.Buffer 678 for i, vi := range v.inner { 679 if len(v.inner) > 1 { 680 fmt.Fprintf(&inner, ",") 681 } 682 fmt.Fprintf(&inner, " ") 683 if i == len(v.inner)-1 { 684 fmt.Fprintf(&inner, "or ") 685 } 686 fmt.Fprintf(&inner, "%s+%d(FP)", vi.name, vi.off) 687 } 688 badf("invalid offset %s; expected %s+%d(FP)%s", expr, v.name, v.off, inner.String()) 689 return 690 } 691 if kind != 0 && kind != vk { 692 var inner bytes.Buffer 693 if len(v.inner) > 0 { 694 fmt.Fprintf(&inner, " containing") 695 for i, vi := range v.inner { 696 if i > 0 && len(v.inner) > 2 { 697 fmt.Fprintf(&inner, ",") 698 } 699 fmt.Fprintf(&inner, " ") 700 if i > 0 && i == len(v.inner)-1 { 701 fmt.Fprintf(&inner, "and ") 702 } 703 fmt.Fprintf(&inner, "%s+%d(FP)", vi.name, vi.off) 704 } 705 } 706 badf("invalid %s of %s; %s is %d-byte value%s", op, expr, vt, vs, inner.String()) 707 } 708 }