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.
5 // Package asmdecl defines an Analyzer that reports mismatches between
6 // assembly files and Go declarations.
21 "golang.org/x/tools/go/analysis"
22 "golang.org/x/tools/go/analysis/passes/internal/analysisutil"
25 const Doc = "report mismatches between assembly files and Go declarations"
27 var Analyzer = &analysis.Analyzer{
33 // 'kind' is a kind of assembly variable.
34 // The kinds 1, 2, 4, 8 stand for values of that size.
37 // These special kinds are not valid sizes.
39 asmString asmKind = 100 + iota
48 // An asmArch describes assembly parameters for an architecture
54 // calculated during initialization
61 // An asmFunc describes the expected variables for a function on a given architecture.
64 size int // size of all arguments
65 vars map[string]*asmVar
66 varByOffset map[int]*asmVar
69 // An asmVar describes a single assembly variable.
80 asmArch386 = asmArch{name: "386", bigEndian: false, stack: "SP", lr: false}
81 asmArchArm = asmArch{name: "arm", bigEndian: false, stack: "R13", lr: true}
82 asmArchArm64 = asmArch{name: "arm64", bigEndian: false, stack: "RSP", lr: true}
83 asmArchAmd64 = asmArch{name: "amd64", bigEndian: false, stack: "SP", lr: false}
84 asmArchMips = asmArch{name: "mips", bigEndian: true, stack: "R29", lr: true}
85 asmArchMipsLE = asmArch{name: "mipsle", bigEndian: false, stack: "R29", lr: true}
86 asmArchMips64 = asmArch{name: "mips64", bigEndian: true, stack: "R29", lr: true}
87 asmArchMips64LE = asmArch{name: "mips64le", bigEndian: false, stack: "R29", lr: true}
88 asmArchPpc64 = asmArch{name: "ppc64", bigEndian: true, stack: "R1", lr: true}
89 asmArchPpc64LE = asmArch{name: "ppc64le", bigEndian: false, stack: "R1", lr: true}
90 asmArchRISCV64 = asmArch{name: "riscv64", bigEndian: false, stack: "SP", lr: true}
91 asmArchS390X = asmArch{name: "s390x", bigEndian: true, stack: "R15", lr: true}
92 asmArchWasm = asmArch{name: "wasm", bigEndian: false, stack: "SP", lr: false}
112 for _, arch := range arches {
113 arch.sizes = types.SizesFor("gc", arch.name)
114 if arch.sizes == nil {
115 // TODO(adonovan): fix: now that asmdecl is not in the standard
116 // library we cannot assume types.SizesFor is consistent with arches.
117 // For now, assume 64-bit norms and print a warning.
118 // But this warning should really be deferred until we attempt to use
119 // arch, which is very unlikely. Better would be
120 // to defer size computation until we have Pass.TypesSizes.
121 arch.sizes = types.SizesFor("gc", "amd64")
122 log.Printf("unknown architecture %s", arch.name)
124 arch.intSize = int(arch.sizes.Sizeof(types.Typ[types.Int]))
125 arch.ptrSize = int(arch.sizes.Sizeof(types.Typ[types.UnsafePointer]))
126 arch.maxAlign = int(arch.sizes.Alignof(types.Typ[types.Int64]))
131 re = regexp.MustCompile
132 asmPlusBuild = re(`//\s+\+build\s+([^\n]+)`)
133 asmTEXT = re(`\bTEXT\b(.*)ยท([^\(]+)\(SB\)(?:\s*,\s*([0-9A-Z|+()]+))?(?:\s*,\s*\$(-?[0-9]+)(?:-([0-9]+))?)?`)
134 asmDATA = re(`\b(DATA|GLOBL)\b`)
135 asmNamedFP = re(`\$?([a-zA-Z0-9_\xFF-\x{10FFFF}]+)(?:\+([0-9]+))\(FP\)`)
136 asmUnnamedFP = re(`[^+\-0-9](([0-9]+)\(FP\))`)
137 asmSP = re(`[^+\-0-9](([0-9]+)\(([A-Z0-9]+)\))`)
138 asmOpcode = re(`^\s*(?:[A-Z0-9a-z_]+:)?\s*([A-Z]+)\s*([^,]*)(?:,\s*(.*))?`)
139 ppc64Suff = re(`([BHWD])(ZU|Z|U|BR)?$`)
140 abiSuff = re(`^(.+)<ABI.+>$`)
143 func run(pass *analysis.Pass) (interface{}, error) {
144 // No work if no assembly files.
146 for _, fname := range pass.OtherFiles {
147 if strings.HasSuffix(fname, ".s") {
148 sfiles = append(sfiles, fname)
155 // Gather declarations. knownFunc[name][arch] is func description.
156 knownFunc := make(map[string]map[string]*asmFunc)
158 for _, f := range pass.Files {
159 for _, decl := range f.Decls {
160 if decl, ok := decl.(*ast.FuncDecl); ok && decl.Body == nil {
161 knownFunc[decl.Name.Name] = asmParseDecl(pass, decl)
167 for _, fname := range sfiles {
168 content, tf, err := analysisutil.ReadFile(pass.Fset, fname)
173 // Determine architecture from file name if possible.
176 for _, a := range arches {
177 if strings.HasSuffix(fname, "_"+a.name+".s") {
184 lines := strings.SplitAfter(string(content), "\n")
188 localSize, argSize int
196 if fn != nil && fn.vars["ret"] != nil && !haveRetArg && len(retLine) > 0 {
198 for _, line := range retLine {
199 pass.Reportf(analysisutil.LineStart(tf, line), "[%s] %s: RET without writing to %d-byte ret+%d(FP)", arch, fnName, v.size, v.off)
204 trimABI := func(fnName string) string {
205 m := abiSuff.FindStringSubmatch(fnName)
211 for lineno, line := range lines {
214 badf := func(format string, args ...interface{}) {
215 pass.Reportf(analysisutil.LineStart(tf, lineno), "[%s] %s: %s", arch, fnName, fmt.Sprintf(format, args...))
219 // Determine architecture from +build line if possible.
220 if m := asmPlusBuild.FindStringSubmatch(line); m != nil {
221 // There can be multiple architectures in a single +build line,
222 // so accumulate them all and then prefer the one that
223 // matches build.Default.GOARCH.
224 var archCandidates []*asmArch
225 for _, fld := range strings.Fields(m[1]) {
226 for _, a := range arches {
228 archCandidates = append(archCandidates, a)
232 for _, a := range archCandidates {
233 if a.name == build.Default.GOARCH {
234 archCandidates = []*asmArch{a}
238 if len(archCandidates) > 0 {
239 arch = archCandidates[0].name
240 archDef = archCandidates[0]
245 // Ignore comments and commented-out code.
246 if i := strings.Index(line, "//"); i >= 0 {
250 if m := asmTEXT.FindStringSubmatch(line); m != nil {
253 // Arch not specified by filename or build tags.
254 // Fall back to build.Default.GOARCH.
255 for _, a := range arches {
256 if a.name == build.Default.GOARCH {
263 log.Printf("%s: cannot determine architecture for assembly file", fname)
268 if pkgPath := strings.TrimSpace(m[1]); pkgPath != "" {
269 // The assembler uses Unicode division slash within
270 // identifiers to represent the directory separator.
271 pkgPath = strings.Replace(pkgPath, "โ", "/", -1)
272 if pkgPath != pass.Pkg.Path() {
273 // log.Printf("%s:%d: [%s] cannot check cross-package assembly function: %s is in package %s", fname, lineno, arch, fnName, pkgPath)
279 // Trim off optional ABI selector.
280 fnName := trimABI(fnName)
282 fn = knownFunc[fnName][arch]
284 size, _ := strconv.Atoi(m[5])
285 if size != fn.size && (flag != "7" && !strings.Contains(flag, "NOSPLIT") || size != 0) {
286 badf("wrong argument size %d; expected $...-%d", size, fn.size)
289 localSize, _ = strconv.Atoi(m[4])
290 localSize += archDef.intSize
291 if archDef.lr && !strings.Contains(flag, "NOFRAME") {
292 // Account for caller's saved LR
293 localSize += archDef.intSize
295 argSize, _ = strconv.Atoi(m[5])
296 noframe = strings.Contains(flag, "NOFRAME")
297 if fn == nil && !strings.Contains(fnName, "<>") && !noframe {
298 badf("function %s missing Go declaration", fnName)
303 } else if strings.Contains(line, "TEXT") && strings.Contains(line, "SB") {
304 // function, but not visible from Go (didn't match asmTEXT), so stop checking
311 if strings.Contains(line, "RET") && !strings.Contains(line, "(SB)") {
312 // RET f(SB) is a tail call. It is okay to not write the results.
313 retLine = append(retLine, lineno)
320 if asmDATA.FindStringSubmatch(line) != nil {
328 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) {
333 if arch == "wasm" && strings.Contains(line, "CallImport") {
334 // CallImport is a call out to magic that can write the result.
338 for _, m := range asmSP.FindAllStringSubmatch(line, -1) {
339 if m[3] != archDef.stack || wroteSP || noframe {
344 off, _ = strconv.Atoi(m[2])
346 if off >= localSize {
348 v := fn.varByOffset[off-localSize]
350 badf("%s should be %s+%d(FP)", m[1], v.name, off-localSize)
354 if off >= localSize+argSize {
355 badf("use of %s points beyond argument frame", m[1])
358 badf("use of %s to access argument frame", m[1])
366 for _, m := range asmUnnamedFP.FindAllStringSubmatch(line, -1) {
367 off, _ := strconv.Atoi(m[2])
368 v := fn.varByOffset[off]
370 badf("use of unnamed argument %s; offset %d is %s+%d(FP)", m[1], off, v.name, v.off)
372 badf("use of unnamed argument %s", m[1])
376 for _, m := range asmNamedFP.FindAllStringSubmatch(line, -1) {
380 off, _ = strconv.Atoi(m[2])
382 if name == "ret" || strings.HasPrefix(name, "ret_") {
387 // Allow argframe+0(FP).
388 if name == "argframe" && off == 0 {
391 v = fn.varByOffset[off]
393 badf("unknown variable %s; offset %d is %s+%d(FP)", name, off, v.name, v.off)
395 badf("unknown variable %s", name)
399 asmCheckVar(badf, fn, line, m[0], off, v, archDef)
407 func asmKindForType(t types.Type, size int) asmKind {
408 switch t := t.Underlying().(type) {
413 case types.Complex64, types.Complex128:
417 case *types.Pointer, *types.Chan, *types.Map, *types.Signature:
421 case *types.Interface:
423 return asmEmptyInterface
434 // A component is an assembly-addressable component of a composite type,
435 // or a composite type itself.
436 type component struct {
441 suffix string // Such as _base for string base, _0_lo for lo half of first element of [1]uint64 on 32 bit machine.
442 outer string // The suffix for immediately containing composite type.
445 func newComponent(suffix string, kind asmKind, typ string, offset, size int, outer string) component {
446 return component{suffix: suffix, kind: kind, typ: typ, offset: offset, size: size, outer: outer}
449 // componentsOfType generates a list of components of type t.
450 // For example, given string, the components are the string itself, the base, and the length.
451 func componentsOfType(arch *asmArch, t types.Type) []component {
452 return appendComponentsRecursive(arch, t, nil, "", 0)
455 // appendComponentsRecursive implements componentsOfType.
456 // Recursion is required to correct handle structs and arrays,
457 // which can contain arbitrary other types.
458 func appendComponentsRecursive(arch *asmArch, t types.Type, cc []component, suffix string, off int) []component {
460 size := int(arch.sizes.Sizeof(t))
461 kind := asmKindForType(t, size)
462 cc = append(cc, newComponent(suffix, kind, s, off, size, suffix))
466 if arch.ptrSize == 4 {
471 cc = append(cc, newComponent(suffix+"_"+w1, 4, "half "+s, off, 4, suffix))
472 cc = append(cc, newComponent(suffix+"_"+w2, 4, "half "+s, off+4, 4, suffix))
475 case asmEmptyInterface:
476 cc = append(cc, newComponent(suffix+"_type", asmKind(arch.ptrSize), "interface type", off, arch.ptrSize, suffix))
477 cc = append(cc, newComponent(suffix+"_data", asmKind(arch.ptrSize), "interface data", off+arch.ptrSize, arch.ptrSize, suffix))
480 cc = append(cc, newComponent(suffix+"_itable", asmKind(arch.ptrSize), "interface itable", off, arch.ptrSize, suffix))
481 cc = append(cc, newComponent(suffix+"_data", asmKind(arch.ptrSize), "interface data", off+arch.ptrSize, arch.ptrSize, suffix))
484 cc = append(cc, newComponent(suffix+"_base", asmKind(arch.ptrSize), "slice base", off, arch.ptrSize, suffix))
485 cc = append(cc, newComponent(suffix+"_len", asmKind(arch.intSize), "slice len", off+arch.ptrSize, arch.intSize, suffix))
486 cc = append(cc, newComponent(suffix+"_cap", asmKind(arch.intSize), "slice cap", off+arch.ptrSize+arch.intSize, arch.intSize, suffix))
489 cc = append(cc, newComponent(suffix+"_base", asmKind(arch.ptrSize), "string base", off, arch.ptrSize, suffix))
490 cc = append(cc, newComponent(suffix+"_len", asmKind(arch.intSize), "string len", off+arch.ptrSize, arch.intSize, suffix))
494 cc = append(cc, newComponent(suffix+"_real", asmKind(fsize), fmt.Sprintf("real(complex%d)", size*8), off, fsize, suffix))
495 cc = append(cc, newComponent(suffix+"_imag", asmKind(fsize), fmt.Sprintf("imag(complex%d)", size*8), off+fsize, fsize, suffix))
498 tu := t.Underlying().(*types.Struct)
499 fields := make([]*types.Var, tu.NumFields())
500 for i := 0; i < tu.NumFields(); i++ {
501 fields[i] = tu.Field(i)
503 offsets := arch.sizes.Offsetsof(fields)
504 for i, f := range fields {
505 cc = appendComponentsRecursive(arch, f.Type(), cc, suffix+"_"+f.Name(), off+int(offsets[i]))
509 tu := t.Underlying().(*types.Array)
511 // Calculate offset of each element array.
512 fields := []*types.Var{
513 types.NewVar(token.NoPos, nil, "fake0", elem),
514 types.NewVar(token.NoPos, nil, "fake1", elem),
516 offsets := arch.sizes.Offsetsof(fields)
517 elemoff := int(offsets[1])
518 for i := 0; i < int(tu.Len()); i++ {
519 cc = appendComponentsRecursive(arch, elem, cc, suffix+"_"+strconv.Itoa(i), off+i*elemoff)
526 // asmParseDecl parses a function decl for expected assembly variables.
527 func asmParseDecl(pass *analysis.Pass, decl *ast.FuncDecl) map[string]*asmFunc {
534 // addParams adds asmVars for each of the parameters in list.
535 // isret indicates whether the list are the arguments or the return values.
536 // TODO(adonovan): simplify by passing (*types.Signature).{Params,Results}
538 addParams := func(list []*ast.Field, isret bool) {
540 for _, fld := range list {
541 t := pass.TypesInfo.Types[fld.Type].Type
543 // Work around https://golang.org/issue/28277.
545 if ell, ok := fld.Type.(*ast.Ellipsis); ok {
546 t = types.NewSlice(pass.TypesInfo.Types[ell.Elt].Type)
550 align := int(arch.sizes.Alignof(t))
551 size := int(arch.sizes.Sizeof(t))
552 offset += -offset & (align - 1)
553 cc := componentsOfType(arch, t)
555 // names is the list of names with this type.
558 // Anonymous args will be called arg, arg1, arg2, ...
559 // Similarly so for return values: ret, ret1, ret2, ...
565 name += strconv.Itoa(argnum)
567 names = []*ast.Ident{ast.NewIdent(name)}
571 // Create variable for each name.
572 for _, id := range names {
574 for _, c := range cc {
575 outer := name + c.outer
577 name: name + c.suffix,
580 off: offset + c.offset,
583 if vo := fn.vars[outer]; vo != nil {
584 vo.inner = append(vo.inner, &v)
587 for i := 0; i < v.size; i++ {
588 fn.varByOffset[v.off+i] = &v
596 m := make(map[string]*asmFunc)
597 for _, arch = range arches {
600 vars: make(map[string]*asmVar),
601 varByOffset: make(map[int]*asmVar),
604 addParams(decl.Type.Params.List, false)
605 if decl.Type.Results != nil && len(decl.Type.Results.List) > 0 {
606 offset += -offset & (arch.maxAlign - 1)
607 addParams(decl.Type.Results.List, true)
616 // asmCheckVar checks a single variable reference.
617 func asmCheckVar(badf func(string, ...interface{}), fn *asmFunc, line, expr string, off int, v *asmVar, archDef *asmArch) {
618 m := asmOpcode.FindStringSubmatch(line)
620 if !strings.HasPrefix(strings.TrimSpace(line), "//") {
621 badf("cannot find assembly opcode")
626 addr := strings.HasPrefix(expr, "$")
628 // Determine operand sizes from instruction.
629 // Typically the suffix suffices, but there are exceptions.
630 var src, dst, kind asmKind
632 switch fn.arch.name + "." + op {
639 case "arm.MOVH", "arm.MOVHU":
641 case "arm.MOVB", "arm.MOVBU":
643 // LEA* opcodes don't really read the second arg.
644 // They just take the address of it.
652 switch fn.arch.name {
654 if strings.HasPrefix(op, "F") && (strings.HasSuffix(op, "D") || strings.HasSuffix(op, "DP")) {
655 // FMOVDP, FXCHD, etc
659 if strings.HasPrefix(op, "P") && strings.HasSuffix(op, "RD") {
660 // PINSRD, PEXTRD, etc
664 if strings.HasPrefix(op, "F") && (strings.HasSuffix(op, "F") || strings.HasSuffix(op, "FP")) {
665 // FMOVFP, FXCHF, etc
669 if strings.HasSuffix(op, "SD") {
670 // MOVSD, SQRTSD, etc
674 if strings.HasSuffix(op, "SS") {
675 // MOVSS, SQRTSS, etc
679 if op == "MOVO" || op == "MOVOU" {
683 if strings.HasPrefix(op, "SET") {
688 switch op[len(op)-1] {
698 case "ppc64", "ppc64le":
699 // Strip standard suffixes to reveal size letter.
700 m := ppc64Suff.FindStringSubmatch(op)
713 case "mips", "mipsle", "mips64", "mips64le":
715 case "MOVB", "MOVBU":
717 case "MOVH", "MOVHU":
719 case "MOVW", "MOVWU", "MOVF":
726 case "MOVB", "MOVBZ":
728 case "MOVH", "MOVHZ":
730 case "MOVW", "MOVWZ", "FMOVS":
732 case "MOVD", "FMOVD":
741 // Determine whether the match we're holding
742 // is the first or second argument.
743 if strings.Index(line, expr) > strings.Index(line, ",") {
753 case asmInterface, asmEmptyInterface, asmString, asmSlice:
754 // allow reference to first word (pointer)
759 // Allow a single instruction to load both parts of a complex.
765 vk = asmKind(archDef.ptrSize)
771 var inner bytes.Buffer
772 for i, vi := range v.inner {
773 if len(v.inner) > 1 {
774 fmt.Fprintf(&inner, ",")
776 fmt.Fprintf(&inner, " ")
777 if i == len(v.inner)-1 {
778 fmt.Fprintf(&inner, "or ")
780 fmt.Fprintf(&inner, "%s+%d(FP)", vi.name, vi.off)
782 badf("invalid offset %s; expected %s+%d(FP)%s", expr, v.name, v.off, inner.String())
785 if kind != 0 && kind != vk {
786 var inner bytes.Buffer
787 if len(v.inner) > 0 {
788 fmt.Fprintf(&inner, " containing")
789 for i, vi := range v.inner {
790 if i > 0 && len(v.inner) > 2 {
791 fmt.Fprintf(&inner, ",")
793 fmt.Fprintf(&inner, " ")
794 if i > 0 && i == len(v.inner)-1 {
795 fmt.Fprintf(&inner, "and ")
797 fmt.Fprintf(&inner, "%s+%d(FP)", vi.name, vi.off)
800 badf("invalid %s of %s; %s is %d-byte value%s", op, expr, vt, vs, inner.String())