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