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