github.com/cockroachdb/tools@v0.0.0-20230222021103-a6d27438930d/go/analysis/passes/asmdecl/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 // Package asmdecl defines an Analyzer that reports mismatches between 6 // assembly files and Go declarations. 7 package asmdecl 8 9 import ( 10 "bytes" 11 "fmt" 12 "go/ast" 13 "go/build" 14 "go/token" 15 "go/types" 16 "log" 17 "regexp" 18 "strconv" 19 "strings" 20 21 "golang.org/x/tools/go/analysis" 22 "golang.org/x/tools/go/analysis/passes/internal/analysisutil" 23 ) 24 25 const Doc = "report mismatches between assembly files and Go declarations" 26 27 var Analyzer = &analysis.Analyzer{ 28 Name: "asmdecl", 29 Doc: Doc, 30 Run: run, 31 } 32 33 // 'kind' is a kind of assembly variable. 34 // The kinds 1, 2, 4, 8 stand for values of that size. 35 type asmKind int 36 37 // These special kinds are not valid sizes. 38 const ( 39 asmString asmKind = 100 + iota 40 asmSlice 41 asmArray 42 asmInterface 43 asmEmptyInterface 44 asmStruct 45 asmComplex 46 ) 47 48 // An asmArch describes assembly parameters for an architecture 49 type asmArch struct { 50 name string 51 bigEndian bool 52 stack string 53 lr bool 54 // retRegs is a list of registers for return value in register ABI (ABIInternal). 55 // For now, as we only check whether we write to any result, here we only need to 56 // include the first integer register and first floating-point register. Accessing 57 // any of them counts as writing to result. 58 retRegs []string 59 // calculated during initialization 60 sizes types.Sizes 61 intSize int 62 ptrSize int 63 maxAlign int 64 } 65 66 // An asmFunc describes the expected variables for a function on a given architecture. 67 type asmFunc struct { 68 arch *asmArch 69 size int // size of all arguments 70 vars map[string]*asmVar 71 varByOffset map[int]*asmVar 72 } 73 74 // An asmVar describes a single assembly variable. 75 type asmVar struct { 76 name string 77 kind asmKind 78 typ string 79 off int 80 size int 81 inner []*asmVar 82 } 83 84 var ( 85 asmArch386 = asmArch{name: "386", bigEndian: false, stack: "SP", lr: false} 86 asmArchArm = asmArch{name: "arm", bigEndian: false, stack: "R13", lr: true} 87 asmArchArm64 = asmArch{name: "arm64", bigEndian: false, stack: "RSP", lr: true, retRegs: []string{"R0", "F0"}} 88 asmArchAmd64 = asmArch{name: "amd64", bigEndian: false, stack: "SP", lr: false, retRegs: []string{"AX", "X0"}} 89 asmArchMips = asmArch{name: "mips", bigEndian: true, stack: "R29", lr: true} 90 asmArchMipsLE = asmArch{name: "mipsle", bigEndian: false, stack: "R29", lr: true} 91 asmArchMips64 = asmArch{name: "mips64", bigEndian: true, stack: "R29", lr: true} 92 asmArchMips64LE = asmArch{name: "mips64le", bigEndian: false, stack: "R29", lr: true} 93 asmArchPpc64 = asmArch{name: "ppc64", bigEndian: true, stack: "R1", lr: true, retRegs: []string{"R3", "F1"}} 94 asmArchPpc64LE = asmArch{name: "ppc64le", bigEndian: false, stack: "R1", lr: true, retRegs: []string{"R3", "F1"}} 95 asmArchRISCV64 = asmArch{name: "riscv64", bigEndian: false, stack: "SP", lr: true, retRegs: []string{"X10", "F10"}} 96 asmArchS390X = asmArch{name: "s390x", bigEndian: true, stack: "R15", lr: true} 97 asmArchWasm = asmArch{name: "wasm", bigEndian: false, stack: "SP", lr: false} 98 99 arches = []*asmArch{ 100 &asmArch386, 101 &asmArchArm, 102 &asmArchArm64, 103 &asmArchAmd64, 104 &asmArchMips, 105 &asmArchMipsLE, 106 &asmArchMips64, 107 &asmArchMips64LE, 108 &asmArchPpc64, 109 &asmArchPpc64LE, 110 &asmArchRISCV64, 111 &asmArchS390X, 112 &asmArchWasm, 113 } 114 ) 115 116 func init() { 117 arches = append(arches, additionalArches()...) 118 for _, arch := range arches { 119 arch.sizes = types.SizesFor("gc", arch.name) 120 if arch.sizes == nil { 121 // TODO(adonovan): fix: now that asmdecl is not in the standard 122 // library we cannot assume types.SizesFor is consistent with arches. 123 // For now, assume 64-bit norms and print a warning. 124 // But this warning should really be deferred until we attempt to use 125 // arch, which is very unlikely. Better would be 126 // to defer size computation until we have Pass.TypesSizes. 127 arch.sizes = types.SizesFor("gc", "amd64") 128 log.Printf("unknown architecture %s", arch.name) 129 } 130 arch.intSize = int(arch.sizes.Sizeof(types.Typ[types.Int])) 131 arch.ptrSize = int(arch.sizes.Sizeof(types.Typ[types.UnsafePointer])) 132 arch.maxAlign = int(arch.sizes.Alignof(types.Typ[types.Int64])) 133 } 134 } 135 136 var ( 137 re = regexp.MustCompile 138 asmPlusBuild = re(`//\s+\+build\s+([^\n]+)`) 139 asmTEXT = re(`\bTEXT\b(.*)·([^\(]+)\(SB\)(?:\s*,\s*([0-9A-Z|+()]+))?(?:\s*,\s*\$(-?[0-9]+)(?:-([0-9]+))?)?`) 140 asmDATA = re(`\b(DATA|GLOBL)\b`) 141 asmNamedFP = re(`\$?([a-zA-Z0-9_\xFF-\x{10FFFF}]+)(?:\+([0-9]+))\(FP\)`) 142 asmUnnamedFP = re(`[^+\-0-9](([0-9]+)\(FP\))`) 143 asmSP = re(`[^+\-0-9](([0-9]+)\(([A-Z0-9]+)\))`) 144 asmOpcode = re(`^\s*(?:[A-Z0-9a-z_]+:)?\s*([A-Z]+)\s*([^,]*)(?:,\s*(.*))?`) 145 ppc64Suff = re(`([BHWD])(ZU|Z|U|BR)?$`) 146 abiSuff = re(`^(.+)<(ABI.+)>$`) 147 ) 148 149 func run(pass *analysis.Pass) (interface{}, error) { 150 // No work if no assembly files. 151 var sfiles []string 152 for _, fname := range pass.OtherFiles { 153 if strings.HasSuffix(fname, ".s") { 154 sfiles = append(sfiles, fname) 155 } 156 } 157 if sfiles == nil { 158 return nil, nil 159 } 160 161 // Gather declarations. knownFunc[name][arch] is func description. 162 knownFunc := make(map[string]map[string]*asmFunc) 163 164 for _, f := range pass.Files { 165 for _, decl := range f.Decls { 166 if decl, ok := decl.(*ast.FuncDecl); ok && decl.Body == nil { 167 knownFunc[decl.Name.Name] = asmParseDecl(pass, decl) 168 } 169 } 170 } 171 172 Files: 173 for _, fname := range sfiles { 174 content, tf, err := analysisutil.ReadFile(pass.Fset, fname) 175 if err != nil { 176 return nil, err 177 } 178 179 // Determine architecture from file name if possible. 180 var arch string 181 var archDef *asmArch 182 for _, a := range arches { 183 if strings.HasSuffix(fname, "_"+a.name+".s") { 184 arch = a.name 185 archDef = a 186 break 187 } 188 } 189 190 lines := strings.SplitAfter(string(content), "\n") 191 var ( 192 fn *asmFunc 193 fnName string 194 abi string 195 localSize, argSize int 196 wroteSP bool 197 noframe bool 198 haveRetArg bool 199 retLine []int 200 ) 201 202 flushRet := func() { 203 if fn != nil && fn.vars["ret"] != nil && !haveRetArg && len(retLine) > 0 { 204 v := fn.vars["ret"] 205 resultStr := fmt.Sprintf("%d-byte ret+%d(FP)", v.size, v.off) 206 if abi == "ABIInternal" { 207 resultStr = "result register" 208 } 209 for _, line := range retLine { 210 pass.Reportf(analysisutil.LineStart(tf, line), "[%s] %s: RET without writing to %s", arch, fnName, resultStr) 211 } 212 } 213 retLine = nil 214 } 215 trimABI := func(fnName string) (string, string) { 216 m := abiSuff.FindStringSubmatch(fnName) 217 if m != nil { 218 return m[1], m[2] 219 } 220 return fnName, "" 221 } 222 for lineno, line := range lines { 223 lineno++ 224 225 badf := func(format string, args ...interface{}) { 226 pass.Reportf(analysisutil.LineStart(tf, lineno), "[%s] %s: %s", arch, fnName, fmt.Sprintf(format, args...)) 227 } 228 229 if arch == "" { 230 // Determine architecture from +build line if possible. 231 if m := asmPlusBuild.FindStringSubmatch(line); m != nil { 232 // There can be multiple architectures in a single +build line, 233 // so accumulate them all and then prefer the one that 234 // matches build.Default.GOARCH. 235 var archCandidates []*asmArch 236 for _, fld := range strings.Fields(m[1]) { 237 for _, a := range arches { 238 if a.name == fld { 239 archCandidates = append(archCandidates, a) 240 } 241 } 242 } 243 for _, a := range archCandidates { 244 if a.name == build.Default.GOARCH { 245 archCandidates = []*asmArch{a} 246 break 247 } 248 } 249 if len(archCandidates) > 0 { 250 arch = archCandidates[0].name 251 archDef = archCandidates[0] 252 } 253 } 254 } 255 256 // Ignore comments and commented-out code. 257 if i := strings.Index(line, "//"); i >= 0 { 258 line = line[:i] 259 } 260 261 if m := asmTEXT.FindStringSubmatch(line); m != nil { 262 flushRet() 263 if arch == "" { 264 // Arch not specified by filename or build tags. 265 // Fall back to build.Default.GOARCH. 266 for _, a := range arches { 267 if a.name == build.Default.GOARCH { 268 arch = a.name 269 archDef = a 270 break 271 } 272 } 273 if arch == "" { 274 log.Printf("%s: cannot determine architecture for assembly file", fname) 275 continue Files 276 } 277 } 278 fnName = m[2] 279 if pkgPath := strings.TrimSpace(m[1]); pkgPath != "" { 280 // The assembler uses Unicode division slash within 281 // identifiers to represent the directory separator. 282 pkgPath = strings.Replace(pkgPath, "∕", "/", -1) 283 if pkgPath != pass.Pkg.Path() { 284 // log.Printf("%s:%d: [%s] cannot check cross-package assembly function: %s is in package %s", fname, lineno, arch, fnName, pkgPath) 285 fn = nil 286 fnName = "" 287 abi = "" 288 continue 289 } 290 } 291 // Trim off optional ABI selector. 292 fnName, abi = trimABI(fnName) 293 flag := m[3] 294 fn = knownFunc[fnName][arch] 295 if fn != nil { 296 size, _ := strconv.Atoi(m[5]) 297 if size != fn.size && (flag != "7" && !strings.Contains(flag, "NOSPLIT") || size != 0) { 298 badf("wrong argument size %d; expected $...-%d", size, fn.size) 299 } 300 } 301 localSize, _ = strconv.Atoi(m[4]) 302 localSize += archDef.intSize 303 if archDef.lr && !strings.Contains(flag, "NOFRAME") { 304 // Account for caller's saved LR 305 localSize += archDef.intSize 306 } 307 argSize, _ = strconv.Atoi(m[5]) 308 noframe = strings.Contains(flag, "NOFRAME") 309 if fn == nil && !strings.Contains(fnName, "<>") && !noframe { 310 badf("function %s missing Go declaration", fnName) 311 } 312 wroteSP = false 313 haveRetArg = false 314 continue 315 } else if strings.Contains(line, "TEXT") && strings.Contains(line, "SB") { 316 // function, but not visible from Go (didn't match asmTEXT), so stop checking 317 flushRet() 318 fn = nil 319 fnName = "" 320 abi = "" 321 continue 322 } 323 324 if strings.Contains(line, "RET") && !strings.Contains(line, "(SB)") { 325 // RET f(SB) is a tail call. It is okay to not write the results. 326 retLine = append(retLine, lineno) 327 } 328 329 if fnName == "" { 330 continue 331 } 332 333 if asmDATA.FindStringSubmatch(line) != nil { 334 fn = nil 335 } 336 337 if archDef == nil { 338 continue 339 } 340 341 if strings.Contains(line, ", "+archDef.stack) || strings.Contains(line, ",\t"+archDef.stack) || strings.Contains(line, "NOP "+archDef.stack) || strings.Contains(line, "NOP\t"+archDef.stack) { 342 wroteSP = true 343 continue 344 } 345 346 if arch == "wasm" && strings.Contains(line, "CallImport") { 347 // CallImport is a call out to magic that can write the result. 348 haveRetArg = true 349 } 350 351 if abi == "ABIInternal" && !haveRetArg { 352 for _, reg := range archDef.retRegs { 353 if strings.Contains(line, reg) { 354 haveRetArg = true 355 break 356 } 357 } 358 } 359 360 for _, m := range asmSP.FindAllStringSubmatch(line, -1) { 361 if m[3] != archDef.stack || wroteSP || noframe { 362 continue 363 } 364 off := 0 365 if m[1] != "" { 366 off, _ = strconv.Atoi(m[2]) 367 } 368 if off >= localSize { 369 if fn != nil { 370 v := fn.varByOffset[off-localSize] 371 if v != nil { 372 badf("%s should be %s+%d(FP)", m[1], v.name, off-localSize) 373 continue 374 } 375 } 376 if off >= localSize+argSize { 377 badf("use of %s points beyond argument frame", m[1]) 378 continue 379 } 380 badf("use of %s to access argument frame", m[1]) 381 } 382 } 383 384 if fn == nil { 385 continue 386 } 387 388 for _, m := range asmUnnamedFP.FindAllStringSubmatch(line, -1) { 389 off, _ := strconv.Atoi(m[2]) 390 v := fn.varByOffset[off] 391 if v != nil { 392 badf("use of unnamed argument %s; offset %d is %s+%d(FP)", m[1], off, v.name, v.off) 393 } else { 394 badf("use of unnamed argument %s", m[1]) 395 } 396 } 397 398 for _, m := range asmNamedFP.FindAllStringSubmatch(line, -1) { 399 name := m[1] 400 off := 0 401 if m[2] != "" { 402 off, _ = strconv.Atoi(m[2]) 403 } 404 if name == "ret" || strings.HasPrefix(name, "ret_") { 405 haveRetArg = true 406 } 407 v := fn.vars[name] 408 if v == nil { 409 // Allow argframe+0(FP). 410 if name == "argframe" && off == 0 { 411 continue 412 } 413 v = fn.varByOffset[off] 414 if v != nil { 415 badf("unknown variable %s; offset %d is %s+%d(FP)", name, off, v.name, v.off) 416 } else { 417 badf("unknown variable %s", name) 418 } 419 continue 420 } 421 asmCheckVar(badf, fn, line, m[0], off, v, archDef) 422 } 423 } 424 flushRet() 425 } 426 return nil, nil 427 } 428 429 func asmKindForType(t types.Type, size int) asmKind { 430 switch t := t.Underlying().(type) { 431 case *types.Basic: 432 switch t.Kind() { 433 case types.String: 434 return asmString 435 case types.Complex64, types.Complex128: 436 return asmComplex 437 } 438 return asmKind(size) 439 case *types.Pointer, *types.Chan, *types.Map, *types.Signature: 440 return asmKind(size) 441 case *types.Struct: 442 return asmStruct 443 case *types.Interface: 444 if t.Empty() { 445 return asmEmptyInterface 446 } 447 return asmInterface 448 case *types.Array: 449 return asmArray 450 case *types.Slice: 451 return asmSlice 452 } 453 panic("unreachable") 454 } 455 456 // A component is an assembly-addressable component of a composite type, 457 // or a composite type itself. 458 type component struct { 459 size int 460 offset int 461 kind asmKind 462 typ string 463 suffix string // Such as _base for string base, _0_lo for lo half of first element of [1]uint64 on 32 bit machine. 464 outer string // The suffix for immediately containing composite type. 465 } 466 467 func newComponent(suffix string, kind asmKind, typ string, offset, size int, outer string) component { 468 return component{suffix: suffix, kind: kind, typ: typ, offset: offset, size: size, outer: outer} 469 } 470 471 // componentsOfType generates a list of components of type t. 472 // For example, given string, the components are the string itself, the base, and the length. 473 func componentsOfType(arch *asmArch, t types.Type) []component { 474 return appendComponentsRecursive(arch, t, nil, "", 0) 475 } 476 477 // appendComponentsRecursive implements componentsOfType. 478 // Recursion is required to correct handle structs and arrays, 479 // which can contain arbitrary other types. 480 func appendComponentsRecursive(arch *asmArch, t types.Type, cc []component, suffix string, off int) []component { 481 s := t.String() 482 size := int(arch.sizes.Sizeof(t)) 483 kind := asmKindForType(t, size) 484 cc = append(cc, newComponent(suffix, kind, s, off, size, suffix)) 485 486 switch kind { 487 case 8: 488 if arch.ptrSize == 4 { 489 w1, w2 := "lo", "hi" 490 if arch.bigEndian { 491 w1, w2 = w2, w1 492 } 493 cc = append(cc, newComponent(suffix+"_"+w1, 4, "half "+s, off, 4, suffix)) 494 cc = append(cc, newComponent(suffix+"_"+w2, 4, "half "+s, off+4, 4, suffix)) 495 } 496 497 case asmEmptyInterface: 498 cc = append(cc, newComponent(suffix+"_type", asmKind(arch.ptrSize), "interface type", off, arch.ptrSize, suffix)) 499 cc = append(cc, newComponent(suffix+"_data", asmKind(arch.ptrSize), "interface data", off+arch.ptrSize, arch.ptrSize, suffix)) 500 501 case asmInterface: 502 cc = append(cc, newComponent(suffix+"_itable", asmKind(arch.ptrSize), "interface itable", off, arch.ptrSize, suffix)) 503 cc = append(cc, newComponent(suffix+"_data", asmKind(arch.ptrSize), "interface data", off+arch.ptrSize, arch.ptrSize, suffix)) 504 505 case asmSlice: 506 cc = append(cc, newComponent(suffix+"_base", asmKind(arch.ptrSize), "slice base", off, arch.ptrSize, suffix)) 507 cc = append(cc, newComponent(suffix+"_len", asmKind(arch.intSize), "slice len", off+arch.ptrSize, arch.intSize, suffix)) 508 cc = append(cc, newComponent(suffix+"_cap", asmKind(arch.intSize), "slice cap", off+arch.ptrSize+arch.intSize, arch.intSize, suffix)) 509 510 case asmString: 511 cc = append(cc, newComponent(suffix+"_base", asmKind(arch.ptrSize), "string base", off, arch.ptrSize, suffix)) 512 cc = append(cc, newComponent(suffix+"_len", asmKind(arch.intSize), "string len", off+arch.ptrSize, arch.intSize, suffix)) 513 514 case asmComplex: 515 fsize := size / 2 516 cc = append(cc, newComponent(suffix+"_real", asmKind(fsize), fmt.Sprintf("real(complex%d)", size*8), off, fsize, suffix)) 517 cc = append(cc, newComponent(suffix+"_imag", asmKind(fsize), fmt.Sprintf("imag(complex%d)", size*8), off+fsize, fsize, suffix)) 518 519 case asmStruct: 520 tu := t.Underlying().(*types.Struct) 521 fields := make([]*types.Var, tu.NumFields()) 522 for i := 0; i < tu.NumFields(); i++ { 523 fields[i] = tu.Field(i) 524 } 525 offsets := arch.sizes.Offsetsof(fields) 526 for i, f := range fields { 527 cc = appendComponentsRecursive(arch, f.Type(), cc, suffix+"_"+f.Name(), off+int(offsets[i])) 528 } 529 530 case asmArray: 531 tu := t.Underlying().(*types.Array) 532 elem := tu.Elem() 533 // Calculate offset of each element array. 534 fields := []*types.Var{ 535 types.NewVar(token.NoPos, nil, "fake0", elem), 536 types.NewVar(token.NoPos, nil, "fake1", elem), 537 } 538 offsets := arch.sizes.Offsetsof(fields) 539 elemoff := int(offsets[1]) 540 for i := 0; i < int(tu.Len()); i++ { 541 cc = appendComponentsRecursive(arch, elem, cc, suffix+"_"+strconv.Itoa(i), off+i*elemoff) 542 } 543 } 544 545 return cc 546 } 547 548 // asmParseDecl parses a function decl for expected assembly variables. 549 func asmParseDecl(pass *analysis.Pass, decl *ast.FuncDecl) map[string]*asmFunc { 550 var ( 551 arch *asmArch 552 fn *asmFunc 553 offset int 554 ) 555 556 // addParams adds asmVars for each of the parameters in list. 557 // isret indicates whether the list are the arguments or the return values. 558 // TODO(adonovan): simplify by passing (*types.Signature).{Params,Results} 559 // instead of list. 560 addParams := func(list []*ast.Field, isret bool) { 561 argnum := 0 562 for _, fld := range list { 563 t := pass.TypesInfo.Types[fld.Type].Type 564 565 // Work around https://golang.org/issue/28277. 566 if t == nil { 567 if ell, ok := fld.Type.(*ast.Ellipsis); ok { 568 t = types.NewSlice(pass.TypesInfo.Types[ell.Elt].Type) 569 } 570 } 571 572 align := int(arch.sizes.Alignof(t)) 573 size := int(arch.sizes.Sizeof(t)) 574 offset += -offset & (align - 1) 575 cc := componentsOfType(arch, t) 576 577 // names is the list of names with this type. 578 names := fld.Names 579 if len(names) == 0 { 580 // Anonymous args will be called arg, arg1, arg2, ... 581 // Similarly so for return values: ret, ret1, ret2, ... 582 name := "arg" 583 if isret { 584 name = "ret" 585 } 586 if argnum > 0 { 587 name += strconv.Itoa(argnum) 588 } 589 names = []*ast.Ident{ast.NewIdent(name)} 590 } 591 argnum += len(names) 592 593 // Create variable for each name. 594 for _, id := range names { 595 name := id.Name 596 for _, c := range cc { 597 outer := name + c.outer 598 v := asmVar{ 599 name: name + c.suffix, 600 kind: c.kind, 601 typ: c.typ, 602 off: offset + c.offset, 603 size: c.size, 604 } 605 if vo := fn.vars[outer]; vo != nil { 606 vo.inner = append(vo.inner, &v) 607 } 608 fn.vars[v.name] = &v 609 for i := 0; i < v.size; i++ { 610 fn.varByOffset[v.off+i] = &v 611 } 612 } 613 offset += size 614 } 615 } 616 } 617 618 m := make(map[string]*asmFunc) 619 for _, arch = range arches { 620 fn = &asmFunc{ 621 arch: arch, 622 vars: make(map[string]*asmVar), 623 varByOffset: make(map[int]*asmVar), 624 } 625 offset = 0 626 addParams(decl.Type.Params.List, false) 627 if decl.Type.Results != nil && len(decl.Type.Results.List) > 0 { 628 offset += -offset & (arch.maxAlign - 1) 629 addParams(decl.Type.Results.List, true) 630 } 631 fn.size = offset 632 m[arch.name] = fn 633 } 634 635 return m 636 } 637 638 // asmCheckVar checks a single variable reference. 639 func asmCheckVar(badf func(string, ...interface{}), fn *asmFunc, line, expr string, off int, v *asmVar, archDef *asmArch) { 640 m := asmOpcode.FindStringSubmatch(line) 641 if m == nil { 642 if !strings.HasPrefix(strings.TrimSpace(line), "//") { 643 badf("cannot find assembly opcode") 644 } 645 return 646 } 647 648 addr := strings.HasPrefix(expr, "$") 649 650 // Determine operand sizes from instruction. 651 // Typically the suffix suffices, but there are exceptions. 652 var src, dst, kind asmKind 653 op := m[1] 654 switch fn.arch.name + "." + op { 655 case "386.FMOVLP": 656 src, dst = 8, 4 657 case "arm.MOVD": 658 src = 8 659 case "arm.MOVW": 660 src = 4 661 case "arm.MOVH", "arm.MOVHU": 662 src = 2 663 case "arm.MOVB", "arm.MOVBU": 664 src = 1 665 // LEA* opcodes don't really read the second arg. 666 // They just take the address of it. 667 case "386.LEAL": 668 dst = 4 669 addr = true 670 case "amd64.LEAQ": 671 dst = 8 672 addr = true 673 default: 674 switch fn.arch.name { 675 case "386", "amd64": 676 if strings.HasPrefix(op, "F") && (strings.HasSuffix(op, "D") || strings.HasSuffix(op, "DP")) { 677 // FMOVDP, FXCHD, etc 678 src = 8 679 break 680 } 681 if strings.HasPrefix(op, "P") && strings.HasSuffix(op, "RD") { 682 // PINSRD, PEXTRD, etc 683 src = 4 684 break 685 } 686 if strings.HasPrefix(op, "F") && (strings.HasSuffix(op, "F") || strings.HasSuffix(op, "FP")) { 687 // FMOVFP, FXCHF, etc 688 src = 4 689 break 690 } 691 if strings.HasSuffix(op, "SD") { 692 // MOVSD, SQRTSD, etc 693 src = 8 694 break 695 } 696 if strings.HasSuffix(op, "SS") { 697 // MOVSS, SQRTSS, etc 698 src = 4 699 break 700 } 701 if op == "MOVO" || op == "MOVOU" { 702 src = 16 703 break 704 } 705 if strings.HasPrefix(op, "SET") { 706 // SETEQ, etc 707 src = 1 708 break 709 } 710 switch op[len(op)-1] { 711 case 'B': 712 src = 1 713 case 'W': 714 src = 2 715 case 'L': 716 src = 4 717 case 'D', 'Q': 718 src = 8 719 } 720 case "ppc64", "ppc64le": 721 // Strip standard suffixes to reveal size letter. 722 m := ppc64Suff.FindStringSubmatch(op) 723 if m != nil { 724 switch m[1][0] { 725 case 'B': 726 src = 1 727 case 'H': 728 src = 2 729 case 'W': 730 src = 4 731 case 'D': 732 src = 8 733 } 734 } 735 case "loong64", "mips", "mipsle", "mips64", "mips64le": 736 switch op { 737 case "MOVB", "MOVBU": 738 src = 1 739 case "MOVH", "MOVHU": 740 src = 2 741 case "MOVW", "MOVWU", "MOVF": 742 src = 4 743 case "MOVV", "MOVD": 744 src = 8 745 } 746 case "s390x": 747 switch op { 748 case "MOVB", "MOVBZ": 749 src = 1 750 case "MOVH", "MOVHZ": 751 src = 2 752 case "MOVW", "MOVWZ", "FMOVS": 753 src = 4 754 case "MOVD", "FMOVD": 755 src = 8 756 } 757 } 758 } 759 if dst == 0 { 760 dst = src 761 } 762 763 // Determine whether the match we're holding 764 // is the first or second argument. 765 if strings.Index(line, expr) > strings.Index(line, ",") { 766 kind = dst 767 } else { 768 kind = src 769 } 770 771 vk := v.kind 772 vs := v.size 773 vt := v.typ 774 switch vk { 775 case asmInterface, asmEmptyInterface, asmString, asmSlice: 776 // allow reference to first word (pointer) 777 vk = v.inner[0].kind 778 vs = v.inner[0].size 779 vt = v.inner[0].typ 780 case asmComplex: 781 // Allow a single instruction to load both parts of a complex. 782 if int(kind) == vs { 783 kind = asmComplex 784 } 785 } 786 if addr { 787 vk = asmKind(archDef.ptrSize) 788 vs = archDef.ptrSize 789 vt = "address" 790 } 791 792 if off != v.off { 793 var inner bytes.Buffer 794 for i, vi := range v.inner { 795 if len(v.inner) > 1 { 796 fmt.Fprintf(&inner, ",") 797 } 798 fmt.Fprintf(&inner, " ") 799 if i == len(v.inner)-1 { 800 fmt.Fprintf(&inner, "or ") 801 } 802 fmt.Fprintf(&inner, "%s+%d(FP)", vi.name, vi.off) 803 } 804 badf("invalid offset %s; expected %s+%d(FP)%s", expr, v.name, v.off, inner.String()) 805 return 806 } 807 if kind != 0 && kind != vk { 808 var inner bytes.Buffer 809 if len(v.inner) > 0 { 810 fmt.Fprintf(&inner, " containing") 811 for i, vi := range v.inner { 812 if i > 0 && len(v.inner) > 2 { 813 fmt.Fprintf(&inner, ",") 814 } 815 fmt.Fprintf(&inner, " ") 816 if i > 0 && i == len(v.inner)-1 { 817 fmt.Fprintf(&inner, "and ") 818 } 819 fmt.Fprintf(&inner, "%s+%d(FP)", vi.name, vi.off) 820 } 821 } 822 badf("invalid %s of %s; %s is %d-byte value%s", op, expr, vt, vs, inner.String()) 823 } 824 }