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