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 	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 }
    731