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 // Identify mismatches between assembly files and Go func declarations.
21 // 'kind' is a kind of assembly variable.
22 // The kinds 1, 2, 4, 8 stand for values of that size.
25 // These special kinds are not valid sizes.
27 asmString asmKind
= 100 + iota
36 // An asmArch describes assembly parameters for an architecture
42 // calculated during initialization
49 // An asmFunc describes the expected variables for a function on a given architecture.
52 size
int // size of all arguments
53 vars
map[string]*asmVar
54 varByOffset
map[int]*asmVar
57 // An asmVar describes a single assembly variable.
68 asmArch386
= asmArch
{name
: "386", bigEndian
: false, stack
: "SP", lr
: false}
69 asmArchArm
= asmArch
{name
: "arm", bigEndian
: false, stack
: "R13", lr
: true}
70 asmArchArm64
= asmArch
{name
: "arm64", bigEndian
: false, stack
: "RSP", lr
: true}
71 asmArchAmd64
= asmArch
{name
: "amd64", bigEndian
: false, stack
: "SP", lr
: false}
72 asmArchAmd64p32
= asmArch
{name
: "amd64p32", bigEndian
: false, stack
: "SP", lr
: false}
73 asmArchMips
= asmArch
{name
: "mips", bigEndian
: true, stack
: "R29", lr
: true}
74 asmArchMipsLE
= asmArch
{name
: "mipsle", bigEndian
: false, stack
: "R29", lr
: true}
75 asmArchMips64
= asmArch
{name
: "mips64", bigEndian
: true, stack
: "R29", lr
: true}
76 asmArchMips64LE
= asmArch
{name
: "mips64le", bigEndian
: false, stack
: "R29", lr
: true}
77 asmArchPpc64
= asmArch
{name
: "ppc64", bigEndian
: true, stack
: "R1", lr
: true}
78 asmArchPpc64LE
= asmArch
{name
: "ppc64le", bigEndian
: false, stack
: "R1", lr
: true}
79 asmArchS390X
= asmArch
{name
: "s390x", bigEndian
: true, stack
: "R15", lr
: true}
98 for _
, arch
:= range arches
{
99 arch
.sizes
= types
.SizesFor("gc", arch
.name
)
100 if arch
.sizes
== nil {
101 panic("missing SizesFor for gc/" + arch
.name
)
103 arch
.intSize
= int(arch
.sizes
.Sizeof(types
.Typ
[types
.Int
]))
104 arch
.ptrSize
= int(arch
.sizes
.Sizeof(types
.Typ
[types
.UnsafePointer
]))
105 arch
.maxAlign
= int(arch
.sizes
.Alignof(types
.Typ
[types
.Int64
]))
110 re
= regexp
.MustCompile
111 asmPlusBuild
= re(`//\s+\+build\s+([^\n]+)`)
112 asmTEXT
= re(`\bTEXT\b(.*)ยท([^\(]+)\(SB\)(?:\s*,\s*([0-9A-Z|+()]+))?(?:\s*,\s*\$(-?[0-9]+)(?:-([0-9]+))?)?`)
113 asmDATA
= re(`\b(DATA|GLOBL)\b`)
114 asmNamedFP
= re(`([a-zA-Z0-9_\xFF-\x{10FFFF}]+)(?:\+([0-9]+))\(FP\)`)
115 asmUnnamedFP
= re(`[^+\-0-9](([0-9]+)\(FP\))`)
116 asmSP
= re(`[^+\-0-9](([0-9]+)\(([A-Z0-9]+)\))`)
117 asmOpcode
= re(`^\s*(?:[A-Z0-9a-z_]+:)?\s*([A-Z]+)\s*([^,]*)(?:,\s*(.*))?`)
118 ppc64Suff
= re(`([BHWD])(ZU|Z|U|BR)?$`)
121 func asmCheck(pkg
*Package
) {
126 // No work if no assembly files.
127 if !pkg
.hasFileWithSuffix(".s") {
131 // Gather declarations. knownFunc[name][arch] is func description.
132 knownFunc
:= make(map[string]map[string]*asmFunc
)
134 for _
, f
:= range pkg
.files
{
136 for _
, decl
:= range f
.file
.Decls
{
137 if decl
, ok
:= decl
.(*ast
.FuncDecl
); ok
&& decl
.Body
== nil {
138 knownFunc
[decl
.Name
.Name
] = f
.asmParseDecl(decl
)
145 for _
, f
:= range pkg
.files
{
146 if !strings
.HasSuffix(f
.name
, ".s") {
149 Println("Checking file", f
.name
)
151 // Determine architecture from file name if possible.
154 for _
, a
:= range arches
{
155 if strings
.HasSuffix(f
.name
, "_"+a
.name
+".s") {
162 lines
:= strings
.SplitAfter(string(f
.content
), "\n")
166 localSize
, argSize
int
173 if fn
!= nil && fn
.vars
["ret"] != nil && !haveRetArg
&& len(retLine
) > 0 {
175 for _
, line
:= range retLine
{
176 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
)
181 for lineno
, line
:= range lines
{
184 badf
:= func(format
string, args
...interface{}) {
185 f
.Badf(token
.NoPos
, "%s:%d: [%s] %s: %s", f
.name
, lineno
, arch
, fnName
, fmt
.Sprintf(format
, args
...))
189 // Determine architecture from +build line if possible.
190 if m
:= asmPlusBuild
.FindStringSubmatch(line
); m
!= nil {
191 // There can be multiple architectures in a single +build line,
192 // so accumulate them all and then prefer the one that
193 // matches build.Default.GOARCH.
194 var archCandidates
[]*asmArch
195 for _
, fld
:= range strings
.Fields(m
[1]) {
196 for _
, a
:= range arches
{
198 archCandidates
= append(archCandidates
, a
)
202 for _
, a
:= range archCandidates
{
203 if a
.name
== build
.Default
.GOARCH
{
204 archCandidates
= []*asmArch
{a
}
208 if len(archCandidates
) > 0 {
209 arch
= archCandidates
[0].name
210 archDef
= archCandidates
[0]
215 if m
:= asmTEXT
.FindStringSubmatch(line
); m
!= nil {
218 // Arch not specified by filename or build tags.
219 // Fall back to build.Default.GOARCH.
220 for _
, a
:= range arches
{
221 if a
.name
== build
.Default
.GOARCH
{
228 f
.Warnf(token
.NoPos
, "%s: cannot determine architecture for assembly file", f
.name
)
233 if pkgName
:= strings
.TrimSpace(m
[1]); pkgName
!= "" {
234 pathParts
:= strings
.Split(pkgName
, "โ")
235 pkgName
= pathParts
[len(pathParts
)-1]
236 if pkgName
!= f
.pkg
.path
{
237 f
.Warnf(token
.NoPos
, "%s:%d: [%s] cannot check cross-package assembly function: %s is in package %s", f
.name
, lineno
, arch
, fnName
, pkgName
)
243 fn
= knownFunc
[fnName
][arch
]
245 size
, _
:= strconv
.Atoi(m
[5])
247 if size
!= fn
.size
&& (flag
!= "7" && !strings
.Contains(flag
, "NOSPLIT") || size
!= 0) {
248 badf("wrong argument size %d; expected $...-%d", size
, fn
.size
)
251 localSize
, _
= strconv
.Atoi(m
[4])
252 localSize
+= archDef
.intSize
254 // Account for caller's saved LR
255 localSize
+= archDef
.intSize
257 argSize
, _
= strconv
.Atoi(m
[5])
258 if fn
== nil && !strings
.Contains(fnName
, "<>") {
259 badf("function %s missing Go declaration", fnName
)
264 } else if strings
.Contains(line
, "TEXT") && strings
.Contains(line
, "SB") {
265 // function, but not visible from Go (didn't match asmTEXT), so stop checking
272 if strings
.Contains(line
, "RET") {
273 retLine
= append(retLine
, lineno
)
280 if asmDATA
.FindStringSubmatch(line
) != nil {
288 if strings
.Contains(line
, ", "+archDef
.stack
) || strings
.Contains(line
, ",\t"+archDef
.stack
) {
293 for _
, m
:= range asmSP
.FindAllStringSubmatch(line
, -1) {
294 if m
[3] != archDef
.stack || wroteSP
{
299 off
, _
= strconv
.Atoi(m
[2])
301 if off
>= localSize
{
303 v
:= fn
.varByOffset
[off
-localSize
]
305 badf("%s should be %s+%d(FP)", m
[1], v
.name
, off
-localSize
)
309 if off
>= localSize
+argSize
{
310 badf("use of %s points beyond argument frame", m
[1])
313 badf("use of %s to access argument frame", m
[1])
321 for _
, m
:= range asmUnnamedFP
.FindAllStringSubmatch(line
, -1) {
322 off
, _
:= strconv
.Atoi(m
[2])
323 v
:= fn
.varByOffset
[off
]
325 badf("use of unnamed argument %s; offset %d is %s+%d(FP)", m
[1], off
, v
.name
, v
.off
)
327 badf("use of unnamed argument %s", m
[1])
331 for _
, m
:= range asmNamedFP
.FindAllStringSubmatch(line
, -1) {
335 off
, _
= strconv
.Atoi(m
[2])
337 if name
== "ret" || strings
.HasPrefix(name
, "ret_") {
342 // Allow argframe+0(FP).
343 if name
== "argframe" && off
== 0 {
346 v
= fn
.varByOffset
[off
]
348 badf("unknown variable %s; offset %d is %s+%d(FP)", name
, off
, v
.name
, v
.off
)
350 badf("unknown variable %s", name
)
354 asmCheckVar(badf
, fn
, line
, m
[0], off
, v
)
361 func asmKindForType(t types
.Type
, size
int) asmKind
{
362 switch t
:= t
.Underlying().(type) {
367 case types
.Complex64
, types
.Complex128
:
371 case *types
.Pointer
, *types
.Chan
, *types
.Map
, *types
.Signature
:
375 case *types
.Interface
:
377 return asmEmptyInterface
388 // A component is an assembly-addressable component of a composite type,
389 // or a composite type itself.
390 type component
struct {
395 suffix
string // Such as _base for string base, _0_lo for lo half of first element of [1]uint64 on 32 bit machine.
396 outer
string // The suffix for immediately containing composite type.
399 func newComponent(suffix
string, kind asmKind
, typ
string, offset
, size
int, outer
string) component
{
400 return component
{suffix
: suffix
, kind
: kind
, typ
: typ
, offset
: offset
, size
: size
, outer
: outer
}
403 // componentsOfType generates a list of components of type t.
404 // For example, given string, the components are the string itself, the base, and the length.
405 func componentsOfType(arch
*asmArch
, t types
.Type
) []component
{
406 return appendComponentsRecursive(arch
, t
, nil, "", 0)
409 // appendComponentsRecursive implements componentsOfType.
410 // Recursion is required to correct handle structs and arrays,
411 // which can contain arbitrary other types.
412 func appendComponentsRecursive(arch
*asmArch
, t types
.Type
, cc
[]component
, suffix
string, off
int) []component
{
414 size
:= int(arch
.sizes
.Sizeof(t
))
415 kind
:= asmKindForType(t
, size
)
416 cc
= append(cc
, newComponent(suffix
, kind
, s
, off
, size
, suffix
))
420 if arch
.ptrSize
== 4 {
425 cc
= append(cc
, newComponent(suffix
+"_"+w1
, 4, "half "+s
, off
, 4, suffix
))
426 cc
= append(cc
, newComponent(suffix
+"_"+w2
, 4, "half "+s
, off
+4, 4, suffix
))
429 case asmEmptyInterface
:
430 cc
= append(cc
, newComponent(suffix
+"_type", asmKind(arch
.ptrSize
), "interface type", off
, arch
.ptrSize
, suffix
))
431 cc
= append(cc
, newComponent(suffix
+"_data", asmKind(arch
.ptrSize
), "interface data", off
+arch
.ptrSize
, arch
.ptrSize
, suffix
))
434 cc
= append(cc
, newComponent(suffix
+"_itable", asmKind(arch
.ptrSize
), "interface itable", off
, arch
.ptrSize
, suffix
))
435 cc
= append(cc
, newComponent(suffix
+"_data", asmKind(arch
.ptrSize
), "interface data", off
+arch
.ptrSize
, arch
.ptrSize
, suffix
))
438 cc
= append(cc
, newComponent(suffix
+"_base", asmKind(arch
.ptrSize
), "slice base", off
, arch
.ptrSize
, suffix
))
439 cc
= append(cc
, newComponent(suffix
+"_len", asmKind(arch
.intSize
), "slice len", off
+arch
.ptrSize
, arch
.intSize
, suffix
))
440 cc
= append(cc
, newComponent(suffix
+"_cap", asmKind(arch
.intSize
), "slice cap", off
+arch
.ptrSize
+arch
.intSize
, arch
.intSize
, suffix
))
443 cc
= append(cc
, newComponent(suffix
+"_base", asmKind(arch
.ptrSize
), "string base", off
, arch
.ptrSize
, suffix
))
444 cc
= append(cc
, newComponent(suffix
+"_len", asmKind(arch
.intSize
), "string len", off
+arch
.ptrSize
, arch
.intSize
, suffix
))
448 cc
= append(cc
, newComponent(suffix
+"_real", asmKind(fsize
), fmt
.Sprintf("real(complex%d)", size
*8), off
, fsize
, suffix
))
449 cc
= append(cc
, newComponent(suffix
+"_imag", asmKind(fsize
), fmt
.Sprintf("imag(complex%d)", size
*8), off
+fsize
, fsize
, suffix
))
452 tu
:= t
.Underlying().(*types
.Struct
)
453 fields
:= make([]*types
.Var
, tu
.NumFields())
454 for i
:= 0; i
< tu
.NumFields(); i
++ {
455 fields
[i
] = tu
.Field(i
)
457 offsets
:= arch
.sizes
.Offsetsof(fields
)
458 for i
, f
:= range fields
{
459 cc
= appendComponentsRecursive(arch
, f
.Type(), cc
, suffix
+"_"+f
.Name(), off
+int(offsets
[i
]))
463 tu
:= t
.Underlying().(*types
.Array
)
465 // Calculate offset of each element array.
466 fields
:= []*types
.Var
{
467 types
.NewVar(token
.NoPos
, nil, "fake0", elem
),
468 types
.NewVar(token
.NoPos
, nil, "fake1", elem
),
470 offsets
:= arch
.sizes
.Offsetsof(fields
)
471 elemoff
:= int(offsets
[1])
472 for i
:= 0; i
< int(tu
.Len()); i
++ {
473 cc
= appendComponentsRecursive(arch
, elem
, cc
, suffix
+"_"+strconv
.Itoa(i
), i
*elemoff
)
480 // asmParseDecl parses a function decl for expected assembly variables.
481 func (f
*File
) asmParseDecl(decl
*ast
.FuncDecl
) map[string]*asmFunc
{
488 // addParams adds asmVars for each of the parameters in list.
489 // isret indicates whether the list are the arguments or the return values.
490 addParams
:= func(list
[]*ast
.Field
, isret
bool) {
492 for _
, fld
:= range list
{
493 t
:= f
.pkg
.types
[fld
.Type
].Type
494 align
:= int(arch
.sizes
.Alignof(t
))
495 size
:= int(arch
.sizes
.Sizeof(t
))
496 offset
+= -offset
& (align
- 1)
497 cc
:= componentsOfType(arch
, t
)
499 // names is the list of names with this type.
502 // Anonymous args will be called arg, arg1, arg2, ...
503 // Similarly so for return values: ret, ret1, ret2, ...
509 name
+= strconv
.Itoa(argnum
)
511 names
= []*ast
.Ident
{ast
.NewIdent(name
)}
515 // Create variable for each name.
516 for _
, id
:= range names
{
518 for _
, c
:= range cc
{
519 outer
:= name
+ c
.outer
521 name
: name
+ c
.suffix
,
524 off
: offset
+ c
.offset
,
527 if vo
:= fn
.vars
[outer
]; vo
!= nil {
528 vo
.inner
= append(vo
.inner
, &v
)
531 for i
:= 0; i
< v
.size
; i
++ {
532 fn
.varByOffset
[v
.off
+i
] = &v
540 m
:= make(map[string]*asmFunc
)
541 for _
, arch
= range arches
{
544 vars
: make(map[string]*asmVar
),
545 varByOffset
: make(map[int]*asmVar
),
548 addParams(decl
.Type
.Params
.List
, false)
549 if decl
.Type
.Results
!= nil && len(decl
.Type
.Results
.List
) > 0 {
550 offset
+= -offset
& (arch
.maxAlign
- 1)
551 addParams(decl
.Type
.Results
.List
, true)
560 // asmCheckVar checks a single variable reference.
561 func asmCheckVar(badf
func(string, ...interface{}), fn
*asmFunc
, line
, expr
string, off
int, v
*asmVar
) {
562 m
:= asmOpcode
.FindStringSubmatch(line
)
564 if !strings
.HasPrefix(strings
.TrimSpace(line
), "//") {
565 badf("cannot find assembly opcode")
570 // Determine operand sizes from instruction.
571 // Typically the suffix suffices, but there are exceptions.
572 var src
, dst
, kind asmKind
574 switch fn
.arch
.name
+ "." + op
{
581 case "arm.MOVH", "arm.MOVHU":
583 case "arm.MOVB", "arm.MOVBU":
585 // LEA* opcodes don't really read the second arg.
586 // They just take the address of it.
591 case "amd64p32.LEAL":
594 switch fn
.arch
.name
{
596 if strings
.HasPrefix(op
, "F") && (strings
.HasSuffix(op
, "D") || strings
.HasSuffix(op
, "DP")) {
597 // FMOVDP, FXCHD, etc
601 if strings
.HasPrefix(op
, "P") && strings
.HasSuffix(op
, "RD") {
602 // PINSRD, PEXTRD, etc
606 if strings
.HasPrefix(op
, "F") && (strings
.HasSuffix(op
, "F") || strings
.HasSuffix(op
, "FP")) {
607 // FMOVFP, FXCHF, etc
611 if strings
.HasSuffix(op
, "SD") {
612 // MOVSD, SQRTSD, etc
616 if strings
.HasSuffix(op
, "SS") {
617 // MOVSS, SQRTSS, etc
621 if strings
.HasPrefix(op
, "SET") {
626 switch op
[len(op
)-1] {
636 case "ppc64", "ppc64le":
637 // Strip standard suffixes to reveal size letter.
638 m
:= ppc64Suff
.FindStringSubmatch(op
)
651 case "mips", "mipsle", "mips64", "mips64le":
653 case "MOVB", "MOVBU":
655 case "MOVH", "MOVHU":
657 case "MOVW", "MOVWU", "MOVF":
664 case "MOVB", "MOVBZ":
666 case "MOVH", "MOVHZ":
668 case "MOVW", "MOVWZ", "FMOVS":
670 case "MOVD", "FMOVD":
679 // Determine whether the match we're holding
680 // is the first or second argument.
681 if strings
.Index(line
, expr
) > strings
.Index(line
, ",") {
691 case asmInterface
, asmEmptyInterface
, asmString
, asmSlice
:
692 // allow reference to first word (pointer)
699 var inner bytes
.Buffer
700 for i
, vi
:= range v
.inner
{
701 if len(v
.inner
) > 1 {
702 fmt
.Fprintf(&inner
, ",")
704 fmt
.Fprintf(&inner
, " ")
705 if i
== len(v
.inner
)-1 {
706 fmt
.Fprintf(&inner
, "or ")
708 fmt
.Fprintf(&inner
, "%s+%d(FP)", vi
.name
, vi
.off
)
710 badf("invalid offset %s; expected %s+%d(FP)%s", expr
, v
.name
, v
.off
, inner
.String())
713 if kind
!= 0 && kind
!= vk
{
714 var inner bytes
.Buffer
715 if len(v
.inner
) > 0 {
716 fmt
.Fprintf(&inner
, " containing")
717 for i
, vi
:= range v
.inner
{
718 if i
> 0 && len(v
.inner
) > 2 {
719 fmt
.Fprintf(&inner
, ",")
721 fmt
.Fprintf(&inner
, " ")
722 if i
> 0 && i
== len(v
.inner
)-1 {
723 fmt
.Fprintf(&inner
, "and ")
725 fmt
.Fprintf(&inner
, "%s+%d(FP)", vi
.name
, vi
.off
)
728 badf("invalid %s of %s; %s is %d-byte value%s", op
, expr
, vt
, vs
, inner
.String())