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 Run: run,
31 }
32
33
34
35 type asmKind int
36
37
38 const (
39 asmString asmKind = 100 + iota
40 asmSlice
41 asmArray
42 asmInterface
43 asmEmptyInterface
44 asmStruct
45 asmComplex
46 )
47
48
49 type asmArch struct {
50 name string
51 bigEndian bool
52 stack string
53 lr bool
54
55
56
57
58 retRegs []string
59
60 sizes types.Sizes
61 intSize int
62 ptrSize int
63 maxAlign int
64 }
65
66
67 type asmFunc struct {
68 arch *asmArch
69 size int
70 vars map[string]*asmVar
71 varByOffset map[int]*asmVar
72 }
73
74
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
122
123
124
125
126
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
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
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
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
231 if m := asmPlusBuild.FindStringSubmatch(line); m != nil {
232
233
234
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
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
265
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
281
282 pkgPath = strings.Replace(pkgPath, "∕", "/", -1)
283 if pkgPath != pass.Pkg.Path() {
284
285 fn = nil
286 fnName = ""
287 abi = ""
288 continue
289 }
290 }
291
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
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
317 flushRet()
318 fn = nil
319 fnName = ""
320 abi = ""
321 continue
322 }
323
324 if strings.Contains(line, "RET") && !strings.Contains(line, "(SB)") {
325
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
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
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
457
458 type component struct {
459 size int
460 offset int
461 kind asmKind
462 typ string
463 suffix string
464 outer string
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
472
473 func componentsOfType(arch *asmArch, t types.Type) []component {
474 return appendComponentsRecursive(arch, t, nil, "", 0)
475 }
476
477
478
479
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
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
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
557
558
559
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
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
578 names := fld.Names
579 if len(names) == 0 {
580
581
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
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
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
651
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
666
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
678 src = 8
679 break
680 }
681 if strings.HasPrefix(op, "P") && strings.HasSuffix(op, "RD") {
682
683 src = 4
684 break
685 }
686 if strings.HasPrefix(op, "F") && (strings.HasSuffix(op, "F") || strings.HasSuffix(op, "FP")) {
687
688 src = 4
689 break
690 }
691 if strings.HasSuffix(op, "SD") {
692
693 src = 8
694 break
695 }
696 if strings.HasSuffix(op, "SS") {
697
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
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
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
764
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
777 vk = v.inner[0].kind
778 vs = v.inner[0].size
779 vt = v.inner[0].typ
780 case asmComplex:
781
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 }
825
View as plain text