github.com/xushiwei/go@v0.0.0-20130601165731-2b9d83f45bc9/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  	bigEndian bool
    37  }
    38  
    39  // An asmFunc describes the expected variables for a function on a given architecture.
    40  type asmFunc struct {
    41  	arch        *asmArch
    42  	size        int // size of all arguments
    43  	vars        map[string]*asmVar
    44  	varByOffset map[int]*asmVar
    45  }
    46  
    47  // An asmVar describes a single assembly variable.
    48  type asmVar struct {
    49  	name  string
    50  	kind  asmKind
    51  	typ   string
    52  	off   int
    53  	size  int
    54  	inner []*asmVar
    55  }
    56  
    57  var (
    58  	asmArch386   = asmArch{"386", 4, 4, false}
    59  	asmArchArm   = asmArch{"arm", 4, 4, false}
    60  	asmArchAmd64 = asmArch{"amd64", 8, 8, false}
    61  
    62  	arches = []*asmArch{
    63  		&asmArch386,
    64  		&asmArchArm,
    65  		&asmArchAmd64,
    66  	}
    67  )
    68  
    69  var (
    70  	re           = regexp.MustCompile
    71  	asmPlusBuild = re(`//\s+\+build\s+([^\n]+)`)
    72  	asmTEXT      = re(`\bTEXT\b.*ยท([^\(]+)\(SB\)(?:\s*,\s*([0-9]+))?(?:\s*,\s*\$([0-9]+)(?:-([0-9]+))?)?`)
    73  	asmDATA      = re(`\b(DATA|GLOBL)\b`)
    74  	asmNamedFP   = re(`([a-zA-Z0-9_\xFF-\x{10FFFF}]+)(?:\+([0-9]+))\(FP\)`)
    75  	asmUnnamedFP = re(`[^+\-0-9]](([0-9]+)\(FP\))`)
    76  	asmOpcode    = re(`^\s*(?:[A-Z0-9a-z_]+:)?\s*([A-Z]+)\s*([^,]*)(?:,\s*(.*))?`)
    77  )
    78  
    79  func asmCheck(pkg *Package) {
    80  	if !vet("asmdecl") {
    81  		return
    82  	}
    83  
    84  	// No work if no assembly files.
    85  	if !pkg.hasFileWithSuffix(".s") {
    86  		return
    87  	}
    88  
    89  	// Gather declarations. knownFunc[name][arch] is func description.
    90  	knownFunc := make(map[string]map[string]*asmFunc)
    91  
    92  	for _, f := range pkg.files {
    93  		if f.file != nil {
    94  			for _, decl := range f.file.Decls {
    95  				if decl, ok := decl.(*ast.FuncDecl); ok && decl.Body == nil {
    96  					knownFunc[decl.Name.Name] = f.asmParseDecl(decl)
    97  				}
    98  			}
    99  		}
   100  	}
   101  
   102  	var fn *asmFunc
   103  	for _, f := range pkg.files {
   104  		if !strings.HasSuffix(f.name, ".s") {
   105  			continue
   106  		}
   107  		Println("Checking file", f.name)
   108  
   109  		// Determine architecture from file name if possible.
   110  		var arch string
   111  		for _, a := range arches {
   112  			if strings.HasSuffix(f.name, "_"+a.name+".s") {
   113  				arch = a.name
   114  				break
   115  			}
   116  		}
   117  
   118  		lines := strings.SplitAfter(string(f.content), "\n")
   119  		for lineno, line := range lines {
   120  			lineno++
   121  
   122  			warnf := func(format string, args ...interface{}) {
   123  				f.Warnf(token.NoPos, "%s:%d: [%s] %s", f.name, lineno, arch, fmt.Sprintf(format, args...))
   124  			}
   125  
   126  			if arch == "" {
   127  				// Determine architecture from +build line if possible.
   128  				if m := asmPlusBuild.FindStringSubmatch(line); m != nil {
   129  				Fields:
   130  					for _, fld := range strings.Fields(m[1]) {
   131  						for _, a := range arches {
   132  							if a.name == fld {
   133  								arch = a.name
   134  								break Fields
   135  							}
   136  						}
   137  					}
   138  				}
   139  			}
   140  
   141  			if m := asmTEXT.FindStringSubmatch(line); m != nil {
   142  				if arch == "" {
   143  					f.Warnf(token.NoPos, "%s: cannot determine architecture for assembly file", f.name)
   144  					return
   145  				}
   146  				fn = knownFunc[m[1]][arch]
   147  				if fn != nil {
   148  					size, _ := strconv.Atoi(m[4])
   149  					if size != fn.size && (m[2] != "7" || size != 0) {
   150  						warnf("wrong argument size %d; expected $...-%d", size, fn.size)
   151  					}
   152  				}
   153  				continue
   154  			} else if strings.Contains(line, "TEXT") && strings.Contains(line, "SB") {
   155  				// function, but not visible from Go (didn't match asmTEXT), so stop checking
   156  				fn = nil
   157  				continue
   158  			}
   159  
   160  			if asmDATA.FindStringSubmatch(line) != nil {
   161  				fn = nil
   162  			}
   163  			if fn == nil {
   164  				continue
   165  			}
   166  
   167  			for _, m := range asmUnnamedFP.FindAllStringSubmatch(line, -1) {
   168  				warnf("use of unnamed argument %s", m[1])
   169  			}
   170  
   171  			for _, m := range asmNamedFP.FindAllStringSubmatch(line, -1) {
   172  				name := m[1]
   173  				off := 0
   174  				if m[2] != "" {
   175  					off, _ = strconv.Atoi(m[2])
   176  				}
   177  				v := fn.vars[name]
   178  				if v == nil {
   179  					// Allow argframe+0(FP).
   180  					if name == "argframe" && off == 0 {
   181  						continue
   182  					}
   183  					v = fn.varByOffset[off]
   184  					if v != nil {
   185  						warnf("unknown variable %s; offset %d is %s+%d(FP)", name, off, v.name, v.off)
   186  					} else {
   187  						warnf("unknown variable %s", name)
   188  					}
   189  					continue
   190  				}
   191  				asmCheckVar(warnf, fn, line, m[0], off, v)
   192  			}
   193  		}
   194  	}
   195  }
   196  
   197  // asmParseDecl parses a function decl for expected assembly variables.
   198  func (f *File) asmParseDecl(decl *ast.FuncDecl) map[string]*asmFunc {
   199  	var (
   200  		arch   *asmArch
   201  		fn     *asmFunc
   202  		offset int
   203  		failed bool
   204  	)
   205  
   206  	addVar := func(outer string, v asmVar) {
   207  		if vo := fn.vars[outer]; vo != nil {
   208  			vo.inner = append(vo.inner, &v)
   209  		}
   210  		fn.vars[v.name] = &v
   211  		for i := 0; i < v.size; i++ {
   212  			fn.varByOffset[v.off+i] = &v
   213  		}
   214  	}
   215  
   216  	addParams := func(list []*ast.Field) {
   217  		for i, fld := range list {
   218  			// Determine alignment, size, and kind of type in declaration.
   219  			var align, size int
   220  			var kind asmKind
   221  			names := fld.Names
   222  			typ := f.gofmt(fld.Type)
   223  			switch t := fld.Type.(type) {
   224  			default:
   225  				switch typ {
   226  				default:
   227  					f.Warnf(fld.Type.Pos(), "unknown assembly argument type %s", typ)
   228  					failed = true
   229  					return
   230  				case "int8", "uint8", "byte", "bool":
   231  					size = 1
   232  				case "int16", "uint16":
   233  					size = 2
   234  				case "int32", "uint32", "float32":
   235  					size = 4
   236  				case "int64", "uint64", "float64":
   237  					align = arch.ptrSize
   238  					size = 8
   239  				case "int", "uint":
   240  					size = arch.intSize
   241  				case "uintptr", "iword", "Word", "Errno", "unsafe.Pointer":
   242  					size = arch.ptrSize
   243  				case "string":
   244  					size = arch.ptrSize * 2
   245  					align = arch.ptrSize
   246  					kind = asmString
   247  				}
   248  			case *ast.ChanType, *ast.FuncType, *ast.MapType, *ast.StarExpr:
   249  				size = arch.ptrSize
   250  			case *ast.InterfaceType:
   251  				align = arch.ptrSize
   252  				size = 2 * arch.ptrSize
   253  				if len(t.Methods.List) > 0 {
   254  					kind = asmInterface
   255  				} else {
   256  					kind = asmEmptyInterface
   257  				}
   258  			case *ast.ArrayType:
   259  				if t.Len == nil {
   260  					size = arch.ptrSize + 2*arch.intSize
   261  					align = arch.ptrSize
   262  					kind = asmSlice
   263  					break
   264  				}
   265  				f.Warnf(fld.Type.Pos(), "unsupported assembly argument type %s", typ)
   266  				failed = true
   267  			case *ast.StructType:
   268  				f.Warnf(fld.Type.Pos(), "unsupported assembly argument type %s", typ)
   269  				failed = true
   270  			}
   271  			if align == 0 {
   272  				align = size
   273  			}
   274  			if kind == 0 {
   275  				kind = asmKind(size)
   276  			}
   277  			offset += -offset & (align - 1)
   278  
   279  			// Create variable for each name being declared with this type.
   280  			if len(names) == 0 {
   281  				name := "unnamed"
   282  				if decl.Type.Results != nil && len(decl.Type.Results.List) > 0 && &list[0] == &decl.Type.Results.List[0] && i == 0 {
   283  					// Assume assembly will refer to single unnamed result as r.
   284  					name = "ret"
   285  				}
   286  				names = []*ast.Ident{{Name: name}}
   287  			}
   288  			for _, id := range names {
   289  				name := id.Name
   290  				addVar("", asmVar{
   291  					name: name,
   292  					kind: kind,
   293  					typ:  typ,
   294  					off:  offset,
   295  					size: size,
   296  				})
   297  				switch kind {
   298  				case 8:
   299  					if arch.ptrSize == 4 {
   300  						w1, w2 := "lo", "hi"
   301  						if arch.bigEndian {
   302  							w1, w2 = w2, w1
   303  						}
   304  						addVar(name, asmVar{
   305  							name: name + "_" + w1,
   306  							kind: 4,
   307  							typ:  "half " + typ,
   308  							off:  offset,
   309  							size: 4,
   310  						})
   311  						addVar(name, asmVar{
   312  							name: name + "_" + w2,
   313  							kind: 4,
   314  							typ:  "half " + typ,
   315  							off:  offset + 4,
   316  							size: 4,
   317  						})
   318  					}
   319  
   320  				case asmEmptyInterface:
   321  					addVar(name, asmVar{
   322  						name: name + "_type",
   323  						kind: asmKind(arch.ptrSize),
   324  						typ:  "interface type",
   325  						off:  offset,
   326  						size: arch.ptrSize,
   327  					})
   328  					addVar(name, asmVar{
   329  						name: name + "_data",
   330  						kind: asmKind(arch.ptrSize),
   331  						typ:  "interface data",
   332  						off:  offset + arch.ptrSize,
   333  						size: arch.ptrSize,
   334  					})
   335  
   336  				case asmInterface:
   337  					addVar(name, asmVar{
   338  						name: name + "_itable",
   339  						kind: asmKind(arch.ptrSize),
   340  						typ:  "interface itable",
   341  						off:  offset,
   342  						size: arch.ptrSize,
   343  					})
   344  					addVar(name, asmVar{
   345  						name: name + "_data",
   346  						kind: asmKind(arch.ptrSize),
   347  						typ:  "interface data",
   348  						off:  offset + arch.ptrSize,
   349  						size: arch.ptrSize,
   350  					})
   351  
   352  				case asmSlice:
   353  					addVar(name, asmVar{
   354  						name: name + "_base",
   355  						kind: asmKind(arch.ptrSize),
   356  						typ:  "slice base",
   357  						off:  offset,
   358  						size: arch.ptrSize,
   359  					})
   360  					addVar(name, asmVar{
   361  						name: name + "_len",
   362  						kind: asmKind(arch.intSize),
   363  						typ:  "slice len",
   364  						off:  offset + arch.ptrSize,
   365  						size: arch.intSize,
   366  					})
   367  					addVar(name, asmVar{
   368  						name: name + "_cap",
   369  						kind: asmKind(arch.intSize),
   370  						typ:  "slice cap",
   371  						off:  offset + arch.ptrSize + arch.intSize,
   372  						size: arch.intSize,
   373  					})
   374  
   375  				case asmString:
   376  					addVar(name, asmVar{
   377  						name: name + "_base",
   378  						kind: asmKind(arch.ptrSize),
   379  						typ:  "string base",
   380  						off:  offset,
   381  						size: arch.ptrSize,
   382  					})
   383  					addVar(name, asmVar{
   384  						name: name + "_len",
   385  						kind: asmKind(arch.intSize),
   386  						typ:  "string len",
   387  						off:  offset + arch.ptrSize,
   388  						size: arch.intSize,
   389  					})
   390  				}
   391  				offset += size
   392  			}
   393  		}
   394  	}
   395  
   396  	m := make(map[string]*asmFunc)
   397  	for _, arch = range arches {
   398  		fn = &asmFunc{
   399  			arch:        arch,
   400  			vars:        make(map[string]*asmVar),
   401  			varByOffset: make(map[int]*asmVar),
   402  		}
   403  		offset = 0
   404  		addParams(decl.Type.Params.List)
   405  		if decl.Type.Results != nil && len(decl.Type.Results.List) > 0 {
   406  			offset += -offset & (arch.ptrSize - 1)
   407  			addParams(decl.Type.Results.List)
   408  		}
   409  		fn.size = offset
   410  		m[arch.name] = fn
   411  	}
   412  
   413  	if failed {
   414  		return nil
   415  	}
   416  	return m
   417  }
   418  
   419  // asmCheckVar checks a single variable reference.
   420  func asmCheckVar(warnf func(string, ...interface{}), fn *asmFunc, line, expr string, off int, v *asmVar) {
   421  	m := asmOpcode.FindStringSubmatch(line)
   422  	if m == nil {
   423  		warnf("cannot find assembly opcode")
   424  	}
   425  
   426  	// Determine operand sizes from instruction.
   427  	// Typically the suffix suffices, but there are exceptions.
   428  	var src, dst, kind asmKind
   429  	op := m[1]
   430  	switch fn.arch.name + "." + op {
   431  	case "386.FMOVLP":
   432  		src, dst = 8, 4
   433  	case "arm.MOVD":
   434  		src = 8
   435  	case "arm.MOVW":
   436  		src = 4
   437  	case "arm.MOVH", "arm.MOVHU":
   438  		src = 2
   439  	case "arm.MOVB", "arm.MOVBU":
   440  		src = 1
   441  	default:
   442  		if fn.arch.name == "386" || fn.arch.name == "amd64" {
   443  			if strings.HasPrefix(op, "F") && (strings.HasSuffix(op, "D") || strings.HasSuffix(op, "DP")) {
   444  				// FMOVDP, FXCHD, etc
   445  				src = 8
   446  				break
   447  			}
   448  			if strings.HasPrefix(op, "F") && (strings.HasSuffix(op, "F") || strings.HasSuffix(op, "FP")) {
   449  				// FMOVFP, FXCHF, etc
   450  				src = 4
   451  				break
   452  			}
   453  			if strings.HasSuffix(op, "SD") {
   454  				// MOVSD, SQRTSD, etc
   455  				src = 8
   456  				break
   457  			}
   458  			if strings.HasSuffix(op, "SS") {
   459  				// MOVSS, SQRTSS, etc
   460  				src = 4
   461  				break
   462  			}
   463  			if strings.HasPrefix(op, "SET") {
   464  				// SETEQ, etc
   465  				src = 1
   466  				break
   467  			}
   468  			switch op[len(op)-1] {
   469  			case 'B':
   470  				src = 1
   471  			case 'W':
   472  				src = 2
   473  			case 'L':
   474  				src = 4
   475  			case 'D', 'Q':
   476  				src = 8
   477  			}
   478  		}
   479  	}
   480  	if dst == 0 {
   481  		dst = src
   482  	}
   483  
   484  	// Determine whether the match we're holding
   485  	// is the first or second argument.
   486  	if strings.Index(line, expr) > strings.Index(line, ",") {
   487  		kind = dst
   488  	} else {
   489  		kind = src
   490  	}
   491  
   492  	vk := v.kind
   493  	vt := v.typ
   494  	switch vk {
   495  	case asmInterface, asmEmptyInterface, asmString, asmSlice:
   496  		// allow reference to first word (pointer)
   497  		vk = v.inner[0].kind
   498  		vt = v.inner[0].typ
   499  	}
   500  
   501  	if off != v.off {
   502  		var inner bytes.Buffer
   503  		for i, vi := range v.inner {
   504  			if len(v.inner) > 1 {
   505  				fmt.Fprintf(&inner, ",")
   506  			}
   507  			fmt.Fprintf(&inner, " ")
   508  			if i == len(v.inner)-1 {
   509  				fmt.Fprintf(&inner, "or ")
   510  			}
   511  			fmt.Fprintf(&inner, "%s+%d(FP)", vi.name, vi.off)
   512  		}
   513  		warnf("invalid offset %s; expected %s+%d(FP)%s", expr, v.name, v.off, inner.String())
   514  		return
   515  	}
   516  	if kind != 0 && kind != vk {
   517  		var inner bytes.Buffer
   518  		if len(v.inner) > 0 {
   519  			fmt.Fprintf(&inner, " containing")
   520  			for i, vi := range v.inner {
   521  				if i > 0 && len(v.inner) > 2 {
   522  					fmt.Fprintf(&inner, ",")
   523  				}
   524  				fmt.Fprintf(&inner, " ")
   525  				if i > 0 && i == len(v.inner)-1 {
   526  					fmt.Fprintf(&inner, "and ")
   527  				}
   528  				fmt.Fprintf(&inner, "%s+%d(FP)", vi.name, vi.off)
   529  			}
   530  		}
   531  		warnf("invalid %s of %s; %s is %d-byte value%s", op, expr, vt, vk, inner.String())
   532  	}
   533  }