+++ /dev/null
-// Copyright 2013 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Package asmdecl defines an Analyzer that reports mismatches between
-// assembly files and Go declarations.
-package asmdecl
-
-import (
- "bytes"
- "fmt"
- "go/ast"
- "go/build"
- "go/token"
- "go/types"
- "log"
- "regexp"
- "strconv"
- "strings"
-
- "golang.org/x/tools/go/analysis"
- "golang.org/x/tools/go/analysis/passes/internal/analysisutil"
-)
-
-const Doc = "report mismatches between assembly files and Go declarations"
-
-var Analyzer = &analysis.Analyzer{
- Name: "asmdecl",
- Doc: Doc,
- Run: run,
-}
-
-// 'kind' is a kind of assembly variable.
-// The kinds 1, 2, 4, 8 stand for values of that size.
-type asmKind int
-
-// These special kinds are not valid sizes.
-const (
- asmString asmKind = 100 + iota
- asmSlice
- asmArray
- asmInterface
- asmEmptyInterface
- asmStruct
- asmComplex
-)
-
-// An asmArch describes assembly parameters for an architecture
-type asmArch struct {
- name string
- bigEndian bool
- stack string
- lr bool
- // calculated during initialization
- sizes types.Sizes
- intSize int
- ptrSize int
- maxAlign int
-}
-
-// An asmFunc describes the expected variables for a function on a given architecture.
-type asmFunc struct {
- arch *asmArch
- size int // size of all arguments
- vars map[string]*asmVar
- varByOffset map[int]*asmVar
-}
-
-// An asmVar describes a single assembly variable.
-type asmVar struct {
- name string
- kind asmKind
- typ string
- off int
- size int
- inner []*asmVar
-}
-
-var (
- asmArch386 = asmArch{name: "386", bigEndian: false, stack: "SP", lr: false}
- asmArchArm = asmArch{name: "arm", bigEndian: false, stack: "R13", lr: true}
- asmArchArm64 = asmArch{name: "arm64", bigEndian: false, stack: "RSP", lr: true}
- asmArchAmd64 = asmArch{name: "amd64", bigEndian: false, stack: "SP", lr: false}
- asmArchMips = asmArch{name: "mips", bigEndian: true, stack: "R29", lr: true}
- asmArchMipsLE = asmArch{name: "mipsle", bigEndian: false, stack: "R29", lr: true}
- asmArchMips64 = asmArch{name: "mips64", bigEndian: true, stack: "R29", lr: true}
- asmArchMips64LE = asmArch{name: "mips64le", bigEndian: false, stack: "R29", lr: true}
- asmArchPpc64 = asmArch{name: "ppc64", bigEndian: true, stack: "R1", lr: true}
- asmArchPpc64LE = asmArch{name: "ppc64le", bigEndian: false, stack: "R1", lr: true}
- asmArchRISCV64 = asmArch{name: "riscv64", bigEndian: false, stack: "SP", lr: true}
- asmArchS390X = asmArch{name: "s390x", bigEndian: true, stack: "R15", lr: true}
- asmArchWasm = asmArch{name: "wasm", bigEndian: false, stack: "SP", lr: false}
-
- arches = []*asmArch{
- &asmArch386,
- &asmArchArm,
- &asmArchArm64,
- &asmArchAmd64,
- &asmArchMips,
- &asmArchMipsLE,
- &asmArchMips64,
- &asmArchMips64LE,
- &asmArchPpc64,
- &asmArchPpc64LE,
- &asmArchRISCV64,
- &asmArchS390X,
- &asmArchWasm,
- }
-)
-
-func init() {
- for _, arch := range arches {
- arch.sizes = types.SizesFor("gc", arch.name)
- if arch.sizes == nil {
- // TODO(adonovan): fix: now that asmdecl is not in the standard
- // library we cannot assume types.SizesFor is consistent with arches.
- // For now, assume 64-bit norms and print a warning.
- // But this warning should really be deferred until we attempt to use
- // arch, which is very unlikely. Better would be
- // to defer size computation until we have Pass.TypesSizes.
- arch.sizes = types.SizesFor("gc", "amd64")
- log.Printf("unknown architecture %s", arch.name)
- }
- arch.intSize = int(arch.sizes.Sizeof(types.Typ[types.Int]))
- arch.ptrSize = int(arch.sizes.Sizeof(types.Typ[types.UnsafePointer]))
- arch.maxAlign = int(arch.sizes.Alignof(types.Typ[types.Int64]))
- }
-}
-
-var (
- re = regexp.MustCompile
- asmPlusBuild = re(`//\s+\+build\s+([^\n]+)`)
- asmTEXT = re(`\bTEXT\b(.*)·([^\(]+)\(SB\)(?:\s*,\s*([0-9A-Z|+()]+))?(?:\s*,\s*\$(-?[0-9]+)(?:-([0-9]+))?)?`)
- asmDATA = re(`\b(DATA|GLOBL)\b`)
- asmNamedFP = re(`\$?([a-zA-Z0-9_\xFF-\x{10FFFF}]+)(?:\+([0-9]+))\(FP\)`)
- asmUnnamedFP = re(`[^+\-0-9](([0-9]+)\(FP\))`)
- asmSP = re(`[^+\-0-9](([0-9]+)\(([A-Z0-9]+)\))`)
- asmOpcode = re(`^\s*(?:[A-Z0-9a-z_]+:)?\s*([A-Z]+)\s*([^,]*)(?:,\s*(.*))?`)
- ppc64Suff = re(`([BHWD])(ZU|Z|U|BR)?$`)
- abiSuff = re(`^(.+)<ABI.+>$`)
-)
-
-func run(pass *analysis.Pass) (interface{}, error) {
- // No work if no assembly files.
- var sfiles []string
- for _, fname := range pass.OtherFiles {
- if strings.HasSuffix(fname, ".s") {
- sfiles = append(sfiles, fname)
- }
- }
- if sfiles == nil {
- return nil, nil
- }
-
- // Gather declarations. knownFunc[name][arch] is func description.
- knownFunc := make(map[string]map[string]*asmFunc)
-
- for _, f := range pass.Files {
- for _, decl := range f.Decls {
- if decl, ok := decl.(*ast.FuncDecl); ok && decl.Body == nil {
- knownFunc[decl.Name.Name] = asmParseDecl(pass, decl)
- }
- }
- }
-
-Files:
- for _, fname := range sfiles {
- content, tf, err := analysisutil.ReadFile(pass.Fset, fname)
- if err != nil {
- return nil, err
- }
-
- // Determine architecture from file name if possible.
- var arch string
- var archDef *asmArch
- for _, a := range arches {
- if strings.HasSuffix(fname, "_"+a.name+".s") {
- arch = a.name
- archDef = a
- break
- }
- }
-
- lines := strings.SplitAfter(string(content), "\n")
- var (
- fn *asmFunc
- fnName string
- localSize, argSize int
- wroteSP bool
- noframe bool
- haveRetArg bool
- retLine []int
- )
-
- flushRet := func() {
- if fn != nil && fn.vars["ret"] != nil && !haveRetArg && len(retLine) > 0 {
- v := fn.vars["ret"]
- for _, line := range retLine {
- pass.Reportf(analysisutil.LineStart(tf, line), "[%s] %s: RET without writing to %d-byte ret+%d(FP)", arch, fnName, v.size, v.off)
- }
- }
- retLine = nil
- }
- trimABI := func(fnName string) string {
- m := abiSuff.FindStringSubmatch(fnName)
- if m != nil {
- return m[1]
- }
- return fnName
- }
- for lineno, line := range lines {
- lineno++
-
- badf := func(format string, args ...interface{}) {
- pass.Reportf(analysisutil.LineStart(tf, lineno), "[%s] %s: %s", arch, fnName, fmt.Sprintf(format, args...))
- }
-
- if arch == "" {
- // Determine architecture from +build line if possible.
- if m := asmPlusBuild.FindStringSubmatch(line); m != nil {
- // There can be multiple architectures in a single +build line,
- // so accumulate them all and then prefer the one that
- // matches build.Default.GOARCH.
- var archCandidates []*asmArch
- for _, fld := range strings.Fields(m[1]) {
- for _, a := range arches {
- if a.name == fld {
- archCandidates = append(archCandidates, a)
- }
- }
- }
- for _, a := range archCandidates {
- if a.name == build.Default.GOARCH {
- archCandidates = []*asmArch{a}
- break
- }
- }
- if len(archCandidates) > 0 {
- arch = archCandidates[0].name
- archDef = archCandidates[0]
- }
- }
- }
-
- // Ignore comments and commented-out code.
- if i := strings.Index(line, "//"); i >= 0 {
- line = line[:i]
- }
-
- if m := asmTEXT.FindStringSubmatch(line); m != nil {
- flushRet()
- if arch == "" {
- // Arch not specified by filename or build tags.
- // Fall back to build.Default.GOARCH.
- for _, a := range arches {
- if a.name == build.Default.GOARCH {
- arch = a.name
- archDef = a
- break
- }
- }
- if arch == "" {
- log.Printf("%s: cannot determine architecture for assembly file", fname)
- continue Files
- }
- }
- fnName = m[2]
- if pkgPath := strings.TrimSpace(m[1]); pkgPath != "" {
- // The assembler uses Unicode division slash within
- // identifiers to represent the directory separator.
- pkgPath = strings.Replace(pkgPath, "∕", "/", -1)
- if pkgPath != pass.Pkg.Path() {
- // log.Printf("%s:%d: [%s] cannot check cross-package assembly function: %s is in package %s", fname, lineno, arch, fnName, pkgPath)
- fn = nil
- fnName = ""
- continue
- }
- }
- // Trim off optional ABI selector.
- fnName := trimABI(fnName)
- flag := m[3]
- fn = knownFunc[fnName][arch]
- if fn != nil {
- size, _ := strconv.Atoi(m[5])
- if size != fn.size && (flag != "7" && !strings.Contains(flag, "NOSPLIT") || size != 0) {
- badf("wrong argument size %d; expected $...-%d", size, fn.size)
- }
- }
- localSize, _ = strconv.Atoi(m[4])
- localSize += archDef.intSize
- if archDef.lr && !strings.Contains(flag, "NOFRAME") {
- // Account for caller's saved LR
- localSize += archDef.intSize
- }
- argSize, _ = strconv.Atoi(m[5])
- noframe = strings.Contains(flag, "NOFRAME")
- if fn == nil && !strings.Contains(fnName, "<>") && !noframe {
- badf("function %s missing Go declaration", fnName)
- }
- wroteSP = false
- haveRetArg = false
- continue
- } else if strings.Contains(line, "TEXT") && strings.Contains(line, "SB") {
- // function, but not visible from Go (didn't match asmTEXT), so stop checking
- flushRet()
- fn = nil
- fnName = ""
- continue
- }
-
- if strings.Contains(line, "RET") {
- retLine = append(retLine, lineno)
- }
-
- if fnName == "" {
- continue
- }
-
- if asmDATA.FindStringSubmatch(line) != nil {
- fn = nil
- }
-
- if archDef == nil {
- continue
- }
-
- if strings.Contains(line, ", "+archDef.stack) || strings.Contains(line, ",\t"+archDef.stack) || strings.Contains(line, "NOP "+archDef.stack) || strings.Contains(line, "NOP\t"+archDef.stack) {
- wroteSP = true
- continue
- }
-
- if arch == "wasm" && strings.Contains(line, "CallImport") {
- // CallImport is a call out to magic that can write the result.
- haveRetArg = true
- }
-
- for _, m := range asmSP.FindAllStringSubmatch(line, -1) {
- if m[3] != archDef.stack || wroteSP || noframe {
- continue
- }
- off := 0
- if m[1] != "" {
- off, _ = strconv.Atoi(m[2])
- }
- if off >= localSize {
- if fn != nil {
- v := fn.varByOffset[off-localSize]
- if v != nil {
- badf("%s should be %s+%d(FP)", m[1], v.name, off-localSize)
- continue
- }
- }
- if off >= localSize+argSize {
- badf("use of %s points beyond argument frame", m[1])
- continue
- }
- badf("use of %s to access argument frame", m[1])
- }
- }
-
- if fn == nil {
- continue
- }
-
- for _, m := range asmUnnamedFP.FindAllStringSubmatch(line, -1) {
- off, _ := strconv.Atoi(m[2])
- v := fn.varByOffset[off]
- if v != nil {
- badf("use of unnamed argument %s; offset %d is %s+%d(FP)", m[1], off, v.name, v.off)
- } else {
- badf("use of unnamed argument %s", m[1])
- }
- }
-
- for _, m := range asmNamedFP.FindAllStringSubmatch(line, -1) {
- name := m[1]
- off := 0
- if m[2] != "" {
- off, _ = strconv.Atoi(m[2])
- }
- if name == "ret" || strings.HasPrefix(name, "ret_") {
- haveRetArg = true
- }
- v := fn.vars[name]
- if v == nil {
- // Allow argframe+0(FP).
- if name == "argframe" && off == 0 {
- continue
- }
- v = fn.varByOffset[off]
- if v != nil {
- badf("unknown variable %s; offset %d is %s+%d(FP)", name, off, v.name, v.off)
- } else {
- badf("unknown variable %s", name)
- }
- continue
- }
- asmCheckVar(badf, fn, line, m[0], off, v, archDef)
- }
- }
- flushRet()
- }
- return nil, nil
-}
-
-func asmKindForType(t types.Type, size int) asmKind {
- switch t := t.Underlying().(type) {
- case *types.Basic:
- switch t.Kind() {
- case types.String:
- return asmString
- case types.Complex64, types.Complex128:
- return asmComplex
- }
- return asmKind(size)
- case *types.Pointer, *types.Chan, *types.Map, *types.Signature:
- return asmKind(size)
- case *types.Struct:
- return asmStruct
- case *types.Interface:
- if t.Empty() {
- return asmEmptyInterface
- }
- return asmInterface
- case *types.Array:
- return asmArray
- case *types.Slice:
- return asmSlice
- }
- panic("unreachable")
-}
-
-// A component is an assembly-addressable component of a composite type,
-// or a composite type itself.
-type component struct {
- size int
- offset int
- kind asmKind
- typ string
- suffix string // Such as _base for string base, _0_lo for lo half of first element of [1]uint64 on 32 bit machine.
- outer string // The suffix for immediately containing composite type.
-}
-
-func newComponent(suffix string, kind asmKind, typ string, offset, size int, outer string) component {
- return component{suffix: suffix, kind: kind, typ: typ, offset: offset, size: size, outer: outer}
-}
-
-// componentsOfType generates a list of components of type t.
-// For example, given string, the components are the string itself, the base, and the length.
-func componentsOfType(arch *asmArch, t types.Type) []component {
- return appendComponentsRecursive(arch, t, nil, "", 0)
-}
-
-// appendComponentsRecursive implements componentsOfType.
-// Recursion is required to correct handle structs and arrays,
-// which can contain arbitrary other types.
-func appendComponentsRecursive(arch *asmArch, t types.Type, cc []component, suffix string, off int) []component {
- s := t.String()
- size := int(arch.sizes.Sizeof(t))
- kind := asmKindForType(t, size)
- cc = append(cc, newComponent(suffix, kind, s, off, size, suffix))
-
- switch kind {
- case 8:
- if arch.ptrSize == 4 {
- w1, w2 := "lo", "hi"
- if arch.bigEndian {
- w1, w2 = w2, w1
- }
- cc = append(cc, newComponent(suffix+"_"+w1, 4, "half "+s, off, 4, suffix))
- cc = append(cc, newComponent(suffix+"_"+w2, 4, "half "+s, off+4, 4, suffix))
- }
-
- case asmEmptyInterface:
- cc = append(cc, newComponent(suffix+"_type", asmKind(arch.ptrSize), "interface type", off, arch.ptrSize, suffix))
- cc = append(cc, newComponent(suffix+"_data", asmKind(arch.ptrSize), "interface data", off+arch.ptrSize, arch.ptrSize, suffix))
-
- case asmInterface:
- cc = append(cc, newComponent(suffix+"_itable", asmKind(arch.ptrSize), "interface itable", off, arch.ptrSize, suffix))
- cc = append(cc, newComponent(suffix+"_data", asmKind(arch.ptrSize), "interface data", off+arch.ptrSize, arch.ptrSize, suffix))
-
- case asmSlice:
- cc = append(cc, newComponent(suffix+"_base", asmKind(arch.ptrSize), "slice base", off, arch.ptrSize, suffix))
- cc = append(cc, newComponent(suffix+"_len", asmKind(arch.intSize), "slice len", off+arch.ptrSize, arch.intSize, suffix))
- cc = append(cc, newComponent(suffix+"_cap", asmKind(arch.intSize), "slice cap", off+arch.ptrSize+arch.intSize, arch.intSize, suffix))
-
- case asmString:
- cc = append(cc, newComponent(suffix+"_base", asmKind(arch.ptrSize), "string base", off, arch.ptrSize, suffix))
- cc = append(cc, newComponent(suffix+"_len", asmKind(arch.intSize), "string len", off+arch.ptrSize, arch.intSize, suffix))
-
- case asmComplex:
- fsize := size / 2
- cc = append(cc, newComponent(suffix+"_real", asmKind(fsize), fmt.Sprintf("real(complex%d)", size*8), off, fsize, suffix))
- cc = append(cc, newComponent(suffix+"_imag", asmKind(fsize), fmt.Sprintf("imag(complex%d)", size*8), off+fsize, fsize, suffix))
-
- case asmStruct:
- tu := t.Underlying().(*types.Struct)
- fields := make([]*types.Var, tu.NumFields())
- for i := 0; i < tu.NumFields(); i++ {
- fields[i] = tu.Field(i)
- }
- offsets := arch.sizes.Offsetsof(fields)
- for i, f := range fields {
- cc = appendComponentsRecursive(arch, f.Type(), cc, suffix+"_"+f.Name(), off+int(offsets[i]))
- }
-
- case asmArray:
- tu := t.Underlying().(*types.Array)
- elem := tu.Elem()
- // Calculate offset of each element array.
- fields := []*types.Var{
- types.NewVar(token.NoPos, nil, "fake0", elem),
- types.NewVar(token.NoPos, nil, "fake1", elem),
- }
- offsets := arch.sizes.Offsetsof(fields)
- elemoff := int(offsets[1])
- for i := 0; i < int(tu.Len()); i++ {
- cc = appendComponentsRecursive(arch, elem, cc, suffix+"_"+strconv.Itoa(i), off+i*elemoff)
- }
- }
-
- return cc
-}
-
-// asmParseDecl parses a function decl for expected assembly variables.
-func asmParseDecl(pass *analysis.Pass, decl *ast.FuncDecl) map[string]*asmFunc {
- var (
- arch *asmArch
- fn *asmFunc
- offset int
- )
-
- // addParams adds asmVars for each of the parameters in list.
- // isret indicates whether the list are the arguments or the return values.
- // TODO(adonovan): simplify by passing (*types.Signature).{Params,Results}
- // instead of list.
- addParams := func(list []*ast.Field, isret bool) {
- argnum := 0
- for _, fld := range list {
- t := pass.TypesInfo.Types[fld.Type].Type
-
- // Work around https://golang.org/issue/28277.
- if t == nil {
- if ell, ok := fld.Type.(*ast.Ellipsis); ok {
- t = types.NewSlice(pass.TypesInfo.Types[ell.Elt].Type)
- }
- }
-
- align := int(arch.sizes.Alignof(t))
- size := int(arch.sizes.Sizeof(t))
- offset += -offset & (align - 1)
- cc := componentsOfType(arch, t)
-
- // names is the list of names with this type.
- names := fld.Names
- if len(names) == 0 {
- // Anonymous args will be called arg, arg1, arg2, ...
- // Similarly so for return values: ret, ret1, ret2, ...
- name := "arg"
- if isret {
- name = "ret"
- }
- if argnum > 0 {
- name += strconv.Itoa(argnum)
- }
- names = []*ast.Ident{ast.NewIdent(name)}
- }
- argnum += len(names)
-
- // Create variable for each name.
- for _, id := range names {
- name := id.Name
- for _, c := range cc {
- outer := name + c.outer
- v := asmVar{
- name: name + c.suffix,
- kind: c.kind,
- typ: c.typ,
- off: offset + c.offset,
- size: c.size,
- }
- if vo := fn.vars[outer]; vo != nil {
- vo.inner = append(vo.inner, &v)
- }
- fn.vars[v.name] = &v
- for i := 0; i < v.size; i++ {
- fn.varByOffset[v.off+i] = &v
- }
- }
- offset += size
- }
- }
- }
-
- m := make(map[string]*asmFunc)
- for _, arch = range arches {
- fn = &asmFunc{
- arch: arch,
- vars: make(map[string]*asmVar),
- varByOffset: make(map[int]*asmVar),
- }
- offset = 0
- addParams(decl.Type.Params.List, false)
- if decl.Type.Results != nil && len(decl.Type.Results.List) > 0 {
- offset += -offset & (arch.maxAlign - 1)
- addParams(decl.Type.Results.List, true)
- }
- fn.size = offset
- m[arch.name] = fn
- }
-
- return m
-}
-
-// asmCheckVar checks a single variable reference.
-func asmCheckVar(badf func(string, ...interface{}), fn *asmFunc, line, expr string, off int, v *asmVar, archDef *asmArch) {
- m := asmOpcode.FindStringSubmatch(line)
- if m == nil {
- if !strings.HasPrefix(strings.TrimSpace(line), "//") {
- badf("cannot find assembly opcode")
- }
- return
- }
-
- addr := strings.HasPrefix(expr, "$")
-
- // Determine operand sizes from instruction.
- // Typically the suffix suffices, but there are exceptions.
- var src, dst, kind asmKind
- op := m[1]
- switch fn.arch.name + "." + op {
- case "386.FMOVLP":
- src, dst = 8, 4
- case "arm.MOVD":
- src = 8
- case "arm.MOVW":
- src = 4
- case "arm.MOVH", "arm.MOVHU":
- src = 2
- case "arm.MOVB", "arm.MOVBU":
- src = 1
- // LEA* opcodes don't really read the second arg.
- // They just take the address of it.
- case "386.LEAL":
- dst = 4
- addr = true
- case "amd64.LEAQ":
- dst = 8
- addr = true
- default:
- switch fn.arch.name {
- case "386", "amd64":
- if strings.HasPrefix(op, "F") && (strings.HasSuffix(op, "D") || strings.HasSuffix(op, "DP")) {
- // FMOVDP, FXCHD, etc
- src = 8
- break
- }
- if strings.HasPrefix(op, "P") && strings.HasSuffix(op, "RD") {
- // PINSRD, PEXTRD, etc
- src = 4
- break
- }
- if strings.HasPrefix(op, "F") && (strings.HasSuffix(op, "F") || strings.HasSuffix(op, "FP")) {
- // FMOVFP, FXCHF, etc
- src = 4
- break
- }
- if strings.HasSuffix(op, "SD") {
- // MOVSD, SQRTSD, etc
- src = 8
- break
- }
- if strings.HasSuffix(op, "SS") {
- // MOVSS, SQRTSS, etc
- src = 4
- break
- }
- if op == "MOVO" || op == "MOVOU" {
- src = 16
- break
- }
- if strings.HasPrefix(op, "SET") {
- // SETEQ, etc
- src = 1
- break
- }
- switch op[len(op)-1] {
- case 'B':
- src = 1
- case 'W':
- src = 2
- case 'L':
- src = 4
- case 'D', 'Q':
- src = 8
- }
- case "ppc64", "ppc64le":
- // Strip standard suffixes to reveal size letter.
- m := ppc64Suff.FindStringSubmatch(op)
- if m != nil {
- switch m[1][0] {
- case 'B':
- src = 1
- case 'H':
- src = 2
- case 'W':
- src = 4
- case 'D':
- src = 8
- }
- }
- case "mips", "mipsle", "mips64", "mips64le":
- switch op {
- case "MOVB", "MOVBU":
- src = 1
- case "MOVH", "MOVHU":
- src = 2
- case "MOVW", "MOVWU", "MOVF":
- src = 4
- case "MOVV", "MOVD":
- src = 8
- }
- case "s390x":
- switch op {
- case "MOVB", "MOVBZ":
- src = 1
- case "MOVH", "MOVHZ":
- src = 2
- case "MOVW", "MOVWZ", "FMOVS":
- src = 4
- case "MOVD", "FMOVD":
- src = 8
- }
- }
- }
- if dst == 0 {
- dst = src
- }
-
- // Determine whether the match we're holding
- // is the first or second argument.
- if strings.Index(line, expr) > strings.Index(line, ",") {
- kind = dst
- } else {
- kind = src
- }
-
- vk := v.kind
- vs := v.size
- vt := v.typ
- switch vk {
- case asmInterface, asmEmptyInterface, asmString, asmSlice:
- // allow reference to first word (pointer)
- vk = v.inner[0].kind
- vs = v.inner[0].size
- vt = v.inner[0].typ
- case asmComplex:
- // Allow a single instruction to load both parts of a complex.
- if int(kind) == vs {
- kind = asmComplex
- }
- }
- if addr {
- vk = asmKind(archDef.ptrSize)
- vs = archDef.ptrSize
- vt = "address"
- }
-
- if off != v.off {
- var inner bytes.Buffer
- for i, vi := range v.inner {
- if len(v.inner) > 1 {
- fmt.Fprintf(&inner, ",")
- }
- fmt.Fprintf(&inner, " ")
- if i == len(v.inner)-1 {
- fmt.Fprintf(&inner, "or ")
- }
- fmt.Fprintf(&inner, "%s+%d(FP)", vi.name, vi.off)
- }
- badf("invalid offset %s; expected %s+%d(FP)%s", expr, v.name, v.off, inner.String())
- return
- }
- if kind != 0 && kind != vk {
- var inner bytes.Buffer
- if len(v.inner) > 0 {
- fmt.Fprintf(&inner, " containing")
- for i, vi := range v.inner {
- if i > 0 && len(v.inner) > 2 {
- fmt.Fprintf(&inner, ",")
- }
- fmt.Fprintf(&inner, " ")
- if i > 0 && i == len(v.inner)-1 {
- fmt.Fprintf(&inner, "and ")
- }
- fmt.Fprintf(&inner, "%s+%d(FP)", vi.name, vi.off)
- }
- }
- badf("invalid %s of %s; %s is %d-byte value%s", op, expr, vt, vs, inner.String())
- }
-}