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