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 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
124
125
126
127
128
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
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
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
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
233 if m := asmPlusBuild.FindStringSubmatch(line); m != nil {
234
235
236
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
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
267
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
283
284 pkgPath = strings.Replace(pkgPath, "∕", "/", -1)
285 if pkgPath != pass.Pkg.Path() {
286
287 fn = nil
288 fnName = ""
289 abi = ""
290 continue
291 }
292 }
293
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
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
319 flushRet()
320 fn = nil
321 fnName = ""
322 abi = ""
323 continue
324 }
325
326 if strings.Contains(line, "RET") && !strings.Contains(line, "(SB)") {
327
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
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
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
459
460 type component struct {
461 size int
462 offset int
463 kind asmKind
464 typ string
465 suffix string
466 outer string
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
474
475 func componentsOfType(arch *asmArch, t types.Type) []component {
476 return appendComponentsRecursive(arch, t, nil, "", 0)
477 }
478
479
480
481
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
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
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
559
560
561
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
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
580 names := fld.Names
581 if len(names) == 0 {
582
583
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
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
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
653
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
668
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
680 src = 8
681 break
682 }
683 if strings.HasPrefix(op, "P") && strings.HasSuffix(op, "RD") {
684
685 src = 4
686 break
687 }
688 if strings.HasPrefix(op, "F") && (strings.HasSuffix(op, "F") || strings.HasSuffix(op, "FP")) {
689
690 src = 4
691 break
692 }
693 if strings.HasSuffix(op, "SD") {
694
695 src = 8
696 break
697 }
698 if strings.HasSuffix(op, "SS") {
699
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
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
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
766
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
779 vk = v.inner[0].kind
780 vs = v.inner[0].size
781 vt = v.inner[0].typ
782 case asmComplex:
783
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 }
827
View as plain text