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