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