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// Package asmdecl defines an Analyzer that reports mismatches between
6// assembly files and Go declarations.
7package asmdecl
8
9import (
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
25const Doc = "report mismatches between assembly files and Go declarations"
26
27var 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// 'kind' is a kind of assembly variable.
35// The kinds 1, 2, 4, 8 stand for values of that size.
36type asmKind int
37
38// These special kinds are not valid sizes.
39const (
40	asmString asmKind = 100 + iota
41	asmSlice
42	asmArray
43	asmInterface
44	asmEmptyInterface
45	asmStruct
46	asmComplex
47)
48
49// An asmArch describes assembly parameters for an architecture
50type asmArch struct {
51	name      string
52	bigEndian bool
53	stack     string
54	lr        bool
55	// retRegs is a list of registers for return value in register ABI (ABIInternal).
56	// For now, as we only check whether we write to any result, here we only need to
57	// include the first integer register and first floating-point register. Accessing
58	// any of them counts as writing to result.
59	retRegs []string
60	// calculated during initialization
61	sizes    types.Sizes
62	intSize  int
63	ptrSize  int
64	maxAlign int
65}
66
67// An asmFunc describes the expected variables for a function on a given architecture.
68type asmFunc struct {
69	arch        *asmArch
70	size        int // size of all arguments
71	vars        map[string]*asmVar
72	varByOffset map[int]*asmVar
73}
74
75// An asmVar describes a single assembly variable.
76type asmVar struct {
77	name  string
78	kind  asmKind
79	typ   string
80	off   int
81	size  int
82	inner []*asmVar
83}
84
85var (
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
119func init() {
120	for _, arch := range arches {
121		arch.sizes = types.SizesFor("gc", arch.name)
122		if arch.sizes == nil {
123			// TODO(adonovan): fix: now that asmdecl is not in the standard
124			// library we cannot assume types.SizesFor is consistent with arches.
125			// For now, assume 64-bit norms and print a warning.
126			// But this warning should really be deferred until we attempt to use
127			// arch, which is very unlikely. Better would be
128			// to defer size computation until we have Pass.TypesSizes.
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
138var (
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
151func run(pass *analysis.Pass) (interface{}, error) {
152	// No work if no assembly files.
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	// Gather declarations. knownFunc[name][arch] is func description.
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
174Files:
175	for _, fname := range sfiles {
176		content, tf, err := analysisutil.ReadFile(pass, fname)
177		if err != nil {
178			return nil, err
179		}
180
181		// Determine architecture from file name if possible.
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				// Determine architecture from +build line if possible.
233				if m := asmPlusBuild.FindStringSubmatch(line); m != nil {
234					// There can be multiple architectures in a single +build line,
235					// so accumulate them all and then prefer the one that
236					// matches build.Default.GOARCH.
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			// Ignore comments and commented-out code.
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					// Arch not specified by filename or build tags.
267					// Fall back to build.Default.GOARCH.
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					// The assembler uses Unicode division slash within
283					// identifiers to represent the directory separator.
284					pkgPath = strings.Replace(pkgPath, "∕", "/", -1)
285					if pkgPath != pass.Pkg.Path() {
286						// log.Printf("%s:%d: [%s] cannot check cross-package assembly function: %s is in package %s", fname, lineno, arch, fnName, pkgPath)
287						fn = nil
288						fnName = ""
289						abi = ""
290						continue
291					}
292				}
293				// Trim off optional ABI selector.
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					// Account for caller's saved LR
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				// function, but not visible from Go (didn't match asmTEXT), so stop checking
319				flushRet()
320				fn = nil
321				fnName = ""
322				abi = ""
323				continue
324			}
325
326			if strings.Contains(line, "RET") && !strings.Contains(line, "(SB)") {
327				// RET f(SB) is a tail call. It is okay to not write the results.
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				// CallImport is a call out to magic that can write the result.
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					// Allow argframe+0(FP).
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
431func 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// A component is an assembly-addressable component of a composite type,
459// or a composite type itself.
460type component struct {
461	size   int
462	offset int
463	kind   asmKind
464	typ    string
465	suffix string // Such as _base for string base, _0_lo for lo half of first element of [1]uint64 on 32 bit machine.
466	outer  string // The suffix for immediately containing composite type.
467}
468
469func 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// componentsOfType generates a list of components of type t.
474// For example, given string, the components are the string itself, the base, and the length.
475func componentsOfType(arch *asmArch, t types.Type) []component {
476	return appendComponentsRecursive(arch, t, nil, "", 0)
477}
478
479// appendComponentsRecursive implements componentsOfType.
480// Recursion is required to correct handle structs and arrays,
481// which can contain arbitrary other types.
482func 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		// Calculate offset of each element array.
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// asmParseDecl parses a function decl for expected assembly variables.
551func asmParseDecl(pass *analysis.Pass, decl *ast.FuncDecl) map[string]*asmFunc {
552	var (
553		arch   *asmArch
554		fn     *asmFunc
555		offset int
556	)
557
558	// addParams adds asmVars for each of the parameters in list.
559	// isret indicates whether the list are the arguments or the return values.
560	// TODO(adonovan): simplify by passing (*types.Signature).{Params,Results}
561	// instead of list.
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			// Work around https://golang.org/issue/28277.
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			// names is the list of names with this type.
580			names := fld.Names
581			if len(names) == 0 {
582				// Anonymous args will be called arg, arg1, arg2, ...
583				// Similarly so for return values: ret, ret1, ret2, ...
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			// Create variable for each name.
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// asmCheckVar checks a single variable reference.
641func 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	// Determine operand sizes from instruction.
653	// Typically the suffix suffices, but there are exceptions.
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	// LEA* opcodes don't really read the second arg.
668	// They just take the address of it.
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				// FMOVDP, FXCHD, etc
680				src = 8
681				break
682			}
683			if strings.HasPrefix(op, "P") && strings.HasSuffix(op, "RD") {
684				// PINSRD, PEXTRD, etc
685				src = 4
686				break
687			}
688			if strings.HasPrefix(op, "F") && (strings.HasSuffix(op, "F") || strings.HasSuffix(op, "FP")) {
689				// FMOVFP, FXCHF, etc
690				src = 4
691				break
692			}
693			if strings.HasSuffix(op, "SD") {
694				// MOVSD, SQRTSD, etc
695				src = 8
696				break
697			}
698			if strings.HasSuffix(op, "SS") {
699				// MOVSS, SQRTSS, etc
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				// SETEQ, etc
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			// Strip standard suffixes to reveal size letter.
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	// Determine whether the match we're holding
766	// is the first or second argument.
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		// allow reference to first word (pointer)
779		vk = v.inner[0].kind
780		vs = v.inner[0].size
781		vt = v.inner[0].typ
782	case asmComplex:
783		// Allow a single instruction to load both parts of a complex.
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