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