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