github.com/dara-project/godist@v0.0.0-20200823115410-e0c80c8f0c78/src/cmd/vet/asmdecl.go (about)

     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  // Identify mismatches between assembly files and Go func declarations.
     6  
     7  package main
     8  
     9  import (
    10  	"bytes"
    11  	"fmt"
    12  	"go/ast"
    13  	"go/build"
    14  	"go/token"
    15  	"go/types"
    16  	"regexp"
    17  	"strconv"
    18  	"strings"
    19  )
    20  
    21  // 'kind' is a kind of assembly variable.
    22  // The kinds 1, 2, 4, 8 stand for values of that size.
    23  type asmKind int
    24  
    25  // These special kinds are not valid sizes.
    26  const (
    27  	asmString asmKind = 100 + iota
    28  	asmSlice
    29  	asmArray
    30  	asmInterface
    31  	asmEmptyInterface
    32  	asmStruct
    33  	asmComplex
    34  )
    35  
    36  // An asmArch describes assembly parameters for an architecture
    37  type asmArch struct {
    38  	name      string
    39  	bigEndian bool
    40  	stack     string
    41  	lr        bool
    42  	// calculated during initialization
    43  	sizes    types.Sizes
    44  	intSize  int
    45  	ptrSize  int
    46  	maxAlign int
    47  }
    48  
    49  // An asmFunc describes the expected variables for a function on a given architecture.
    50  type asmFunc struct {
    51  	arch        *asmArch
    52  	size        int // size of all arguments
    53  	vars        map[string]*asmVar
    54  	varByOffset map[int]*asmVar
    55  }
    56  
    57  // An asmVar describes a single assembly variable.
    58  type asmVar struct {
    59  	name  string
    60  	kind  asmKind
    61  	typ   string
    62  	off   int
    63  	size  int
    64  	inner []*asmVar
    65  }
    66  
    67  var (
    68  	asmArch386      = asmArch{name: "386", bigEndian: false, stack: "SP", lr: false}
    69  	asmArchArm      = asmArch{name: "arm", bigEndian: false, stack: "R13", lr: true}
    70  	asmArchArm64    = asmArch{name: "arm64", bigEndian: false, stack: "RSP", lr: true}
    71  	asmArchAmd64    = asmArch{name: "amd64", bigEndian: false, stack: "SP", lr: false}
    72  	asmArchAmd64p32 = asmArch{name: "amd64p32", bigEndian: false, stack: "SP", lr: false}
    73  	asmArchMips     = asmArch{name: "mips", bigEndian: true, stack: "R29", lr: true}
    74  	asmArchMipsLE   = asmArch{name: "mipsle", bigEndian: false, stack: "R29", lr: true}
    75  	asmArchMips64   = asmArch{name: "mips64", bigEndian: true, stack: "R29", lr: true}
    76  	asmArchMips64LE = asmArch{name: "mips64le", bigEndian: false, stack: "R29", lr: true}
    77  	asmArchPpc64    = asmArch{name: "ppc64", bigEndian: true, stack: "R1", lr: true}
    78  	asmArchPpc64LE  = asmArch{name: "ppc64le", bigEndian: false, stack: "R1", lr: true}
    79  	asmArchS390X    = asmArch{name: "s390x", bigEndian: true, stack: "R15", lr: true}
    80  
    81  	arches = []*asmArch{
    82  		&asmArch386,
    83  		&asmArchArm,
    84  		&asmArchArm64,
    85  		&asmArchAmd64,
    86  		&asmArchAmd64p32,
    87  		&asmArchMips,
    88  		&asmArchMipsLE,
    89  		&asmArchMips64,
    90  		&asmArchMips64LE,
    91  		&asmArchPpc64,
    92  		&asmArchPpc64LE,
    93  		&asmArchS390X,
    94  	}
    95  )
    96  
    97  func init() {
    98  	for _, arch := range arches {
    99  		arch.sizes = types.SizesFor("gc", arch.name)
   100  		if arch.sizes == nil {
   101  			panic("missing SizesFor for gc/" + arch.name)
   102  		}
   103  		arch.intSize = int(arch.sizes.Sizeof(types.Typ[types.Int]))
   104  		arch.ptrSize = int(arch.sizes.Sizeof(types.Typ[types.UnsafePointer]))
   105  		arch.maxAlign = int(arch.sizes.Alignof(types.Typ[types.Int64]))
   106  	}
   107  }
   108  
   109  var (
   110  	re           = regexp.MustCompile
   111  	asmPlusBuild = re(`//\s+\+build\s+([^\n]+)`)
   112  	asmTEXT      = re(`\bTEXT\b(.*)·([^\(]+)\(SB\)(?:\s*,\s*([0-9A-Z|+()]+))?(?:\s*,\s*\$(-?[0-9]+)(?:-([0-9]+))?)?`)
   113  	asmDATA      = re(`\b(DATA|GLOBL)\b`)
   114  	asmNamedFP   = re(`([a-zA-Z0-9_\xFF-\x{10FFFF}]+)(?:\+([0-9]+))\(FP\)`)
   115  	asmUnnamedFP = re(`[^+\-0-9](([0-9]+)\(FP\))`)
   116  	asmSP        = re(`[^+\-0-9](([0-9]+)\(([A-Z0-9]+)\))`)
   117  	asmOpcode    = re(`^\s*(?:[A-Z0-9a-z_]+:)?\s*([A-Z]+)\s*([^,]*)(?:,\s*(.*))?`)
   118  	ppc64Suff    = re(`([BHWD])(ZU|Z|U|BR)?$`)
   119  )
   120  
   121  func asmCheck(pkg *Package) {
   122  	if !vet("asmdecl") {
   123  		return
   124  	}
   125  
   126  	// No work if no assembly files.
   127  	if !pkg.hasFileWithSuffix(".s") {
   128  		return
   129  	}
   130  
   131  	// Gather declarations. knownFunc[name][arch] is func description.
   132  	knownFunc := make(map[string]map[string]*asmFunc)
   133  
   134  	for _, f := range pkg.files {
   135  		if f.file != nil {
   136  			for _, decl := range f.file.Decls {
   137  				if decl, ok := decl.(*ast.FuncDecl); ok && decl.Body == nil {
   138  					knownFunc[decl.Name.Name] = f.asmParseDecl(decl)
   139  				}
   140  			}
   141  		}
   142  	}
   143  
   144  Files:
   145  	for _, f := range pkg.files {
   146  		if !strings.HasSuffix(f.name, ".s") {
   147  			continue
   148  		}
   149  		Println("Checking file", f.name)
   150  
   151  		// Determine architecture from file name if possible.
   152  		var arch string
   153  		var archDef *asmArch
   154  		for _, a := range arches {
   155  			if strings.HasSuffix(f.name, "_"+a.name+".s") {
   156  				arch = a.name
   157  				archDef = a
   158  				break
   159  			}
   160  		}
   161  
   162  		lines := strings.SplitAfter(string(f.content), "\n")
   163  		var (
   164  			fn                 *asmFunc
   165  			fnName             string
   166  			localSize, argSize int
   167  			wroteSP            bool
   168  			haveRetArg         bool
   169  			retLine            []int
   170  		)
   171  
   172  		flushRet := func() {
   173  			if fn != nil && fn.vars["ret"] != nil && !haveRetArg && len(retLine) > 0 {
   174  				v := fn.vars["ret"]
   175  				for _, line := range retLine {
   176  					f.Badf(token.NoPos, "%s:%d: [%s] %s: RET without writing to %d-byte ret+%d(FP)", f.name, line, arch, fnName, v.size, v.off)
   177  				}
   178  			}
   179  			retLine = nil
   180  		}
   181  		for lineno, line := range lines {
   182  			lineno++
   183  
   184  			badf := func(format string, args ...interface{}) {
   185  				f.Badf(token.NoPos, "%s:%d: [%s] %s: %s", f.name, lineno, arch, fnName, fmt.Sprintf(format, args...))
   186  			}
   187  
   188  			if arch == "" {
   189  				// Determine architecture from +build line if possible.
   190  				if m := asmPlusBuild.FindStringSubmatch(line); m != nil {
   191  					// There can be multiple architectures in a single +build line,
   192  					// so accumulate them all and then prefer the one that
   193  					// matches build.Default.GOARCH.
   194  					var archCandidates []*asmArch
   195  					for _, fld := range strings.Fields(m[1]) {
   196  						for _, a := range arches {
   197  							if a.name == fld {
   198  								archCandidates = append(archCandidates, a)
   199  							}
   200  						}
   201  					}
   202  					for _, a := range archCandidates {
   203  						if a.name == build.Default.GOARCH {
   204  							archCandidates = []*asmArch{a}
   205  							break
   206  						}
   207  					}
   208  					if len(archCandidates) > 0 {
   209  						arch = archCandidates[0].name
   210  						archDef = archCandidates[0]
   211  					}
   212  				}
   213  			}
   214  
   215  			if m := asmTEXT.FindStringSubmatch(line); m != nil {
   216  				flushRet()
   217  				if arch == "" {
   218  					// Arch not specified by filename or build tags.
   219  					// Fall back to build.Default.GOARCH.
   220  					for _, a := range arches {
   221  						if a.name == build.Default.GOARCH {
   222  							arch = a.name
   223  							archDef = a
   224  							break
   225  						}
   226  					}
   227  					if arch == "" {
   228  						f.Warnf(token.NoPos, "%s: cannot determine architecture for assembly file", f.name)
   229  						continue Files
   230  					}
   231  				}
   232  				fnName = m[2]
   233  				if pkgName := strings.TrimSpace(m[1]); pkgName != "" {
   234  					pathParts := strings.Split(pkgName, "∕")
   235  					pkgName = pathParts[len(pathParts)-1]
   236  					if pkgName != f.pkg.path {
   237  						f.Warnf(token.NoPos, "%s:%d: [%s] cannot check cross-package assembly function: %s is in package %s", f.name, lineno, arch, fnName, pkgName)
   238  						fn = nil
   239  						fnName = ""
   240  						continue
   241  					}
   242  				}
   243  				fn = knownFunc[fnName][arch]
   244  				if fn != nil {
   245  					size, _ := strconv.Atoi(m[5])
   246  					flag := m[3]
   247  					if size != fn.size && (flag != "7" && !strings.Contains(flag, "NOSPLIT") || size != 0) {
   248  						badf("wrong argument size %d; expected $...-%d", size, fn.size)
   249  					}
   250  				}
   251  				localSize, _ = strconv.Atoi(m[4])
   252  				localSize += archDef.intSize
   253  				if archDef.lr {
   254  					// Account for caller's saved LR
   255  					localSize += archDef.intSize
   256  				}
   257  				argSize, _ = strconv.Atoi(m[5])
   258  				if fn == nil && !strings.Contains(fnName, "<>") {
   259  					badf("function %s missing Go declaration", fnName)
   260  				}
   261  				wroteSP = false
   262  				haveRetArg = false
   263  				continue
   264  			} else if strings.Contains(line, "TEXT") && strings.Contains(line, "SB") {
   265  				// function, but not visible from Go (didn't match asmTEXT), so stop checking
   266  				flushRet()
   267  				fn = nil
   268  				fnName = ""
   269  				continue
   270  			}
   271  
   272  			if strings.Contains(line, "RET") {
   273  				retLine = append(retLine, lineno)
   274  			}
   275  
   276  			if fnName == "" {
   277  				continue
   278  			}
   279  
   280  			if asmDATA.FindStringSubmatch(line) != nil {
   281  				fn = nil
   282  			}
   283  
   284  			if archDef == nil {
   285  				continue
   286  			}
   287  
   288  			if strings.Contains(line, ", "+archDef.stack) || strings.Contains(line, ",\t"+archDef.stack) {
   289  				wroteSP = true
   290  				continue
   291  			}
   292  
   293  			for _, m := range asmSP.FindAllStringSubmatch(line, -1) {
   294  				if m[3] != archDef.stack || wroteSP {
   295  					continue
   296  				}
   297  				off := 0
   298  				if m[1] != "" {
   299  					off, _ = strconv.Atoi(m[2])
   300  				}
   301  				if off >= localSize {
   302  					if fn != nil {
   303  						v := fn.varByOffset[off-localSize]
   304  						if v != nil {
   305  							badf("%s should be %s+%d(FP)", m[1], v.name, off-localSize)
   306  							continue
   307  						}
   308  					}
   309  					if off >= localSize+argSize {
   310  						badf("use of %s points beyond argument frame", m[1])
   311  						continue
   312  					}
   313  					badf("use of %s to access argument frame", m[1])
   314  				}
   315  			}
   316  
   317  			if fn == nil {
   318  				continue
   319  			}
   320  
   321  			for _, m := range asmUnnamedFP.FindAllStringSubmatch(line, -1) {
   322  				off, _ := strconv.Atoi(m[2])
   323  				v := fn.varByOffset[off]
   324  				if v != nil {
   325  					badf("use of unnamed argument %s; offset %d is %s+%d(FP)", m[1], off, v.name, v.off)
   326  				} else {
   327  					badf("use of unnamed argument %s", m[1])
   328  				}
   329  			}
   330  
   331  			for _, m := range asmNamedFP.FindAllStringSubmatch(line, -1) {
   332  				name := m[1]
   333  				off := 0
   334  				if m[2] != "" {
   335  					off, _ = strconv.Atoi(m[2])
   336  				}
   337  				if name == "ret" || strings.HasPrefix(name, "ret_") {
   338  					haveRetArg = true
   339  				}
   340  				v := fn.vars[name]
   341  				if v == nil {
   342  					// Allow argframe+0(FP).
   343  					if name == "argframe" && off == 0 {
   344  						continue
   345  					}
   346  					v = fn.varByOffset[off]
   347  					if v != nil {
   348  						badf("unknown variable %s; offset %d is %s+%d(FP)", name, off, v.name, v.off)
   349  					} else {
   350  						badf("unknown variable %s", name)
   351  					}
   352  					continue
   353  				}
   354  				asmCheckVar(badf, fn, line, m[0], off, v)
   355  			}
   356  		}
   357  		flushRet()
   358  	}
   359  }
   360  
   361  func asmKindForType(t types.Type, size int) asmKind {
   362  	switch t := t.Underlying().(type) {
   363  	case *types.Basic:
   364  		switch t.Kind() {
   365  		case types.String:
   366  			return asmString
   367  		case types.Complex64, types.Complex128:
   368  			return asmComplex
   369  		}
   370  		return asmKind(size)
   371  	case *types.Pointer, *types.Chan, *types.Map, *types.Signature:
   372  		return asmKind(size)
   373  	case *types.Struct:
   374  		return asmStruct
   375  	case *types.Interface:
   376  		if t.Empty() {
   377  			return asmEmptyInterface
   378  		}
   379  		return asmInterface
   380  	case *types.Array:
   381  		return asmArray
   382  	case *types.Slice:
   383  		return asmSlice
   384  	}
   385  	panic("unreachable")
   386  }
   387  
   388  // A component is an assembly-addressable component of a composite type,
   389  // or a composite type itself.
   390  type component struct {
   391  	size   int
   392  	offset int
   393  	kind   asmKind
   394  	typ    string
   395  	suffix string // Such as _base for string base, _0_lo for lo half of first element of [1]uint64 on 32 bit machine.
   396  	outer  string // The suffix for immediately containing composite type.
   397  }
   398  
   399  func newComponent(suffix string, kind asmKind, typ string, offset, size int, outer string) component {
   400  	return component{suffix: suffix, kind: kind, typ: typ, offset: offset, size: size, outer: outer}
   401  }
   402  
   403  // componentsOfType generates a list of components of type t.
   404  // For example, given string, the components are the string itself, the base, and the length.
   405  func componentsOfType(arch *asmArch, t types.Type) []component {
   406  	return appendComponentsRecursive(arch, t, nil, "", 0)
   407  }
   408  
   409  // appendComponentsRecursive implements componentsOfType.
   410  // Recursion is required to correct handle structs and arrays,
   411  // which can contain arbitrary other types.
   412  func appendComponentsRecursive(arch *asmArch, t types.Type, cc []component, suffix string, off int) []component {
   413  	s := t.String()
   414  	size := int(arch.sizes.Sizeof(t))
   415  	kind := asmKindForType(t, size)
   416  	cc = append(cc, newComponent(suffix, kind, s, off, size, suffix))
   417  
   418  	switch kind {
   419  	case 8:
   420  		if arch.ptrSize == 4 {
   421  			w1, w2 := "lo", "hi"
   422  			if arch.bigEndian {
   423  				w1, w2 = w2, w1
   424  			}
   425  			cc = append(cc, newComponent(suffix+"_"+w1, 4, "half "+s, off, 4, suffix))
   426  			cc = append(cc, newComponent(suffix+"_"+w2, 4, "half "+s, off+4, 4, suffix))
   427  		}
   428  
   429  	case asmEmptyInterface:
   430  		cc = append(cc, newComponent(suffix+"_type", asmKind(arch.ptrSize), "interface type", off, arch.ptrSize, suffix))
   431  		cc = append(cc, newComponent(suffix+"_data", asmKind(arch.ptrSize), "interface data", off+arch.ptrSize, arch.ptrSize, suffix))
   432  
   433  	case asmInterface:
   434  		cc = append(cc, newComponent(suffix+"_itable", asmKind(arch.ptrSize), "interface itable", off, arch.ptrSize, suffix))
   435  		cc = append(cc, newComponent(suffix+"_data", asmKind(arch.ptrSize), "interface data", off+arch.ptrSize, arch.ptrSize, suffix))
   436  
   437  	case asmSlice:
   438  		cc = append(cc, newComponent(suffix+"_base", asmKind(arch.ptrSize), "slice base", off, arch.ptrSize, suffix))
   439  		cc = append(cc, newComponent(suffix+"_len", asmKind(arch.intSize), "slice len", off+arch.ptrSize, arch.intSize, suffix))
   440  		cc = append(cc, newComponent(suffix+"_cap", asmKind(arch.intSize), "slice cap", off+arch.ptrSize+arch.intSize, arch.intSize, suffix))
   441  
   442  	case asmString:
   443  		cc = append(cc, newComponent(suffix+"_base", asmKind(arch.ptrSize), "string base", off, arch.ptrSize, suffix))
   444  		cc = append(cc, newComponent(suffix+"_len", asmKind(arch.intSize), "string len", off+arch.ptrSize, arch.intSize, suffix))
   445  
   446  	case asmComplex:
   447  		fsize := size / 2
   448  		cc = append(cc, newComponent(suffix+"_real", asmKind(fsize), fmt.Sprintf("real(complex%d)", size*8), off, fsize, suffix))
   449  		cc = append(cc, newComponent(suffix+"_imag", asmKind(fsize), fmt.Sprintf("imag(complex%d)", size*8), off+fsize, fsize, suffix))
   450  
   451  	case asmStruct:
   452  		tu := t.Underlying().(*types.Struct)
   453  		fields := make([]*types.Var, tu.NumFields())
   454  		for i := 0; i < tu.NumFields(); i++ {
   455  			fields[i] = tu.Field(i)
   456  		}
   457  		offsets := arch.sizes.Offsetsof(fields)
   458  		for i, f := range fields {
   459  			cc = appendComponentsRecursive(arch, f.Type(), cc, suffix+"_"+f.Name(), off+int(offsets[i]))
   460  		}
   461  
   462  	case asmArray:
   463  		tu := t.Underlying().(*types.Array)
   464  		elem := tu.Elem()
   465  		// Calculate offset of each element array.
   466  		fields := []*types.Var{
   467  			types.NewVar(token.NoPos, nil, "fake0", elem),
   468  			types.NewVar(token.NoPos, nil, "fake1", elem),
   469  		}
   470  		offsets := arch.sizes.Offsetsof(fields)
   471  		elemoff := int(offsets[1])
   472  		for i := 0; i < int(tu.Len()); i++ {
   473  			cc = appendComponentsRecursive(arch, elem, cc, suffix+"_"+strconv.Itoa(i), i*elemoff)
   474  		}
   475  	}
   476  
   477  	return cc
   478  }
   479  
   480  // asmParseDecl parses a function decl for expected assembly variables.
   481  func (f *File) asmParseDecl(decl *ast.FuncDecl) map[string]*asmFunc {
   482  	var (
   483  		arch   *asmArch
   484  		fn     *asmFunc
   485  		offset int
   486  	)
   487  
   488  	// addParams adds asmVars for each of the parameters in list.
   489  	// isret indicates whether the list are the arguments or the return values.
   490  	addParams := func(list []*ast.Field, isret bool) {
   491  		argnum := 0
   492  		for _, fld := range list {
   493  			t := f.pkg.types[fld.Type].Type
   494  			align := int(arch.sizes.Alignof(t))
   495  			size := int(arch.sizes.Sizeof(t))
   496  			offset += -offset & (align - 1)
   497  			cc := componentsOfType(arch, t)
   498  
   499  			// names is the list of names with this type.
   500  			names := fld.Names
   501  			if len(names) == 0 {
   502  				// Anonymous args will be called arg, arg1, arg2, ...
   503  				// Similarly so for return values: ret, ret1, ret2, ...
   504  				name := "arg"
   505  				if isret {
   506  					name = "ret"
   507  				}
   508  				if argnum > 0 {
   509  					name += strconv.Itoa(argnum)
   510  				}
   511  				names = []*ast.Ident{ast.NewIdent(name)}
   512  			}
   513  			argnum += len(names)
   514  
   515  			// Create variable for each name.
   516  			for _, id := range names {
   517  				name := id.Name
   518  				for _, c := range cc {
   519  					outer := name + c.outer
   520  					v := asmVar{
   521  						name: name + c.suffix,
   522  						kind: c.kind,
   523  						typ:  c.typ,
   524  						off:  offset + c.offset,
   525  						size: c.size,
   526  					}
   527  					if vo := fn.vars[outer]; vo != nil {
   528  						vo.inner = append(vo.inner, &v)
   529  					}
   530  					fn.vars[v.name] = &v
   531  					for i := 0; i < v.size; i++ {
   532  						fn.varByOffset[v.off+i] = &v
   533  					}
   534  				}
   535  				offset += size
   536  			}
   537  		}
   538  	}
   539  
   540  	m := make(map[string]*asmFunc)
   541  	for _, arch = range arches {
   542  		fn = &asmFunc{
   543  			arch:        arch,
   544  			vars:        make(map[string]*asmVar),
   545  			varByOffset: make(map[int]*asmVar),
   546  		}
   547  		offset = 0
   548  		addParams(decl.Type.Params.List, false)
   549  		if decl.Type.Results != nil && len(decl.Type.Results.List) > 0 {
   550  			offset += -offset & (arch.maxAlign - 1)
   551  			addParams(decl.Type.Results.List, true)
   552  		}
   553  		fn.size = offset
   554  		m[arch.name] = fn
   555  	}
   556  
   557  	return m
   558  }
   559  
   560  // asmCheckVar checks a single variable reference.
   561  func asmCheckVar(badf func(string, ...interface{}), fn *asmFunc, line, expr string, off int, v *asmVar) {
   562  	m := asmOpcode.FindStringSubmatch(line)
   563  	if m == nil {
   564  		if !strings.HasPrefix(strings.TrimSpace(line), "//") {
   565  			badf("cannot find assembly opcode")
   566  		}
   567  		return
   568  	}
   569  
   570  	// Determine operand sizes from instruction.
   571  	// Typically the suffix suffices, but there are exceptions.
   572  	var src, dst, kind asmKind
   573  	op := m[1]
   574  	switch fn.arch.name + "." + op {
   575  	case "386.FMOVLP":
   576  		src, dst = 8, 4
   577  	case "arm.MOVD":
   578  		src = 8
   579  	case "arm.MOVW":
   580  		src = 4
   581  	case "arm.MOVH", "arm.MOVHU":
   582  		src = 2
   583  	case "arm.MOVB", "arm.MOVBU":
   584  		src = 1
   585  	// LEA* opcodes don't really read the second arg.
   586  	// They just take the address of it.
   587  	case "386.LEAL":
   588  		dst = 4
   589  	case "amd64.LEAQ":
   590  		dst = 8
   591  	case "amd64p32.LEAL":
   592  		dst = 4
   593  	default:
   594  		switch fn.arch.name {
   595  		case "386", "amd64":
   596  			if strings.HasPrefix(op, "F") && (strings.HasSuffix(op, "D") || strings.HasSuffix(op, "DP")) {
   597  				// FMOVDP, FXCHD, etc
   598  				src = 8
   599  				break
   600  			}
   601  			if strings.HasPrefix(op, "P") && strings.HasSuffix(op, "RD") {
   602  				// PINSRD, PEXTRD, etc
   603  				src = 4
   604  				break
   605  			}
   606  			if strings.HasPrefix(op, "F") && (strings.HasSuffix(op, "F") || strings.HasSuffix(op, "FP")) {
   607  				// FMOVFP, FXCHF, etc
   608  				src = 4
   609  				break
   610  			}
   611  			if strings.HasSuffix(op, "SD") {
   612  				// MOVSD, SQRTSD, etc
   613  				src = 8
   614  				break
   615  			}
   616  			if strings.HasSuffix(op, "SS") {
   617  				// MOVSS, SQRTSS, etc
   618  				src = 4
   619  				break
   620  			}
   621  			if strings.HasPrefix(op, "SET") {
   622  				// SETEQ, etc
   623  				src = 1
   624  				break
   625  			}
   626  			switch op[len(op)-1] {
   627  			case 'B':
   628  				src = 1
   629  			case 'W':
   630  				src = 2
   631  			case 'L':
   632  				src = 4
   633  			case 'D', 'Q':
   634  				src = 8
   635  			}
   636  		case "ppc64", "ppc64le":
   637  			// Strip standard suffixes to reveal size letter.
   638  			m := ppc64Suff.FindStringSubmatch(op)
   639  			if m != nil {
   640  				switch m[1][0] {
   641  				case 'B':
   642  					src = 1
   643  				case 'H':
   644  					src = 2
   645  				case 'W':
   646  					src = 4
   647  				case 'D':
   648  					src = 8
   649  				}
   650  			}
   651  		case "mips", "mipsle", "mips64", "mips64le":
   652  			switch op {
   653  			case "MOVB", "MOVBU":
   654  				src = 1
   655  			case "MOVH", "MOVHU":
   656  				src = 2
   657  			case "MOVW", "MOVWU", "MOVF":
   658  				src = 4
   659  			case "MOVV", "MOVD":
   660  				src = 8
   661  			}
   662  		case "s390x":
   663  			switch op {
   664  			case "MOVB", "MOVBZ":
   665  				src = 1
   666  			case "MOVH", "MOVHZ":
   667  				src = 2
   668  			case "MOVW", "MOVWZ", "FMOVS":
   669  				src = 4
   670  			case "MOVD", "FMOVD":
   671  				src = 8
   672  			}
   673  		}
   674  	}
   675  	if dst == 0 {
   676  		dst = src
   677  	}
   678  
   679  	// Determine whether the match we're holding
   680  	// is the first or second argument.
   681  	if strings.Index(line, expr) > strings.Index(line, ",") {
   682  		kind = dst
   683  	} else {
   684  		kind = src
   685  	}
   686  
   687  	vk := v.kind
   688  	vs := v.size
   689  	vt := v.typ
   690  	switch vk {
   691  	case asmInterface, asmEmptyInterface, asmString, asmSlice:
   692  		// allow reference to first word (pointer)
   693  		vk = v.inner[0].kind
   694  		vs = v.inner[0].size
   695  		vt = v.inner[0].typ
   696  	}
   697  
   698  	if off != v.off {
   699  		var inner bytes.Buffer
   700  		for i, vi := range v.inner {
   701  			if len(v.inner) > 1 {
   702  				fmt.Fprintf(&inner, ",")
   703  			}
   704  			fmt.Fprintf(&inner, " ")
   705  			if i == len(v.inner)-1 {
   706  				fmt.Fprintf(&inner, "or ")
   707  			}
   708  			fmt.Fprintf(&inner, "%s+%d(FP)", vi.name, vi.off)
   709  		}
   710  		badf("invalid offset %s; expected %s+%d(FP)%s", expr, v.name, v.off, inner.String())
   711  		return
   712  	}
   713  	if kind != 0 && kind != vk {
   714  		var inner bytes.Buffer
   715  		if len(v.inner) > 0 {
   716  			fmt.Fprintf(&inner, " containing")
   717  			for i, vi := range v.inner {
   718  				if i > 0 && len(v.inner) > 2 {
   719  					fmt.Fprintf(&inner, ",")
   720  				}
   721  				fmt.Fprintf(&inner, " ")
   722  				if i > 0 && i == len(v.inner)-1 {
   723  					fmt.Fprintf(&inner, "and ")
   724  				}
   725  				fmt.Fprintf(&inner, "%s+%d(FP)", vi.name, vi.off)
   726  			}
   727  		}
   728  		badf("invalid %s of %s; %s is %d-byte value%s", op, expr, vt, vs, inner.String())
   729  	}
   730  }