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