Home | History | Annotate | Download | only in vet
      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 }
    726