go/analysis/passes/asmdecl: support in-register result in ABIInternal

With register ABI, for an ABIInternal function, understand that
the result may be written to a register instead of to memory.

TODO: argument area size calculation is still not fixed for
register ABI.

Change-Id: I109a5dc03ff0acc4bc04502710daf32efd1b08f6
Reviewed-on: https://go-review.googlesource.com/c/tools/+/339250
Trust: Cherry Mui <cherryyz@google.com>
Run-TryBot: Cherry Mui <cherryyz@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Than McIntosh <thanm@google.com>
This commit is contained in:
Cherry Mui 2021-08-02 18:20:32 -04:00
parent fcc905b221
commit 337cebd2c1
3 changed files with 40 additions and 8 deletions

View file

@ -51,6 +51,11 @@ type asmArch struct {
bigEndian bool bigEndian bool
stack string stack string
lr bool lr bool
// retRegs is a list of registers for return value in register ABI (ABIInternal).
// For now, as we only check whether we write to any result, here we only need to
// include the first integer register and first floating-point register. Accessing
// any of them counts as writing to result.
retRegs []string
// calculated during initialization // calculated during initialization
sizes types.Sizes sizes types.Sizes
intSize int intSize int
@ -79,8 +84,8 @@ type asmVar struct {
var ( var (
asmArch386 = asmArch{name: "386", bigEndian: false, stack: "SP", lr: false} asmArch386 = asmArch{name: "386", bigEndian: false, stack: "SP", lr: false}
asmArchArm = asmArch{name: "arm", bigEndian: false, stack: "R13", lr: true} asmArchArm = asmArch{name: "arm", bigEndian: false, stack: "R13", lr: true}
asmArchArm64 = asmArch{name: "arm64", bigEndian: false, stack: "RSP", lr: true} asmArchArm64 = asmArch{name: "arm64", bigEndian: false, stack: "RSP", lr: true, retRegs: []string{"R0", "F0"}}
asmArchAmd64 = asmArch{name: "amd64", bigEndian: false, stack: "SP", lr: false} asmArchAmd64 = asmArch{name: "amd64", bigEndian: false, stack: "SP", lr: false, retRegs: []string{"AX", "X0"}}
asmArchMips = asmArch{name: "mips", bigEndian: true, stack: "R29", lr: true} asmArchMips = asmArch{name: "mips", bigEndian: true, stack: "R29", lr: true}
asmArchMipsLE = asmArch{name: "mipsle", bigEndian: false, stack: "R29", lr: true} asmArchMipsLE = asmArch{name: "mipsle", bigEndian: false, stack: "R29", lr: true}
asmArchMips64 = asmArch{name: "mips64", bigEndian: true, stack: "R29", lr: true} asmArchMips64 = asmArch{name: "mips64", bigEndian: true, stack: "R29", lr: true}
@ -137,7 +142,7 @@ var (
asmSP = re(`[^+\-0-9](([0-9]+)\(([A-Z0-9]+)\))`) asmSP = re(`[^+\-0-9](([0-9]+)\(([A-Z0-9]+)\))`)
asmOpcode = re(`^\s*(?:[A-Z0-9a-z_]+:)?\s*([A-Z]+)\s*([^,]*)(?:,\s*(.*))?`) asmOpcode = re(`^\s*(?:[A-Z0-9a-z_]+:)?\s*([A-Z]+)\s*([^,]*)(?:,\s*(.*))?`)
ppc64Suff = re(`([BHWD])(ZU|Z|U|BR)?$`) ppc64Suff = re(`([BHWD])(ZU|Z|U|BR)?$`)
abiSuff = re(`^(.+)<ABI.+>$`) abiSuff = re(`^(.+)<(ABI.+)>$`)
) )
func run(pass *analysis.Pass) (interface{}, error) { func run(pass *analysis.Pass) (interface{}, error) {
@ -185,6 +190,7 @@ Files:
var ( var (
fn *asmFunc fn *asmFunc
fnName string fnName string
abi string
localSize, argSize int localSize, argSize int
wroteSP bool wroteSP bool
noframe bool noframe bool
@ -195,18 +201,22 @@ Files:
flushRet := func() { flushRet := func() {
if fn != nil && fn.vars["ret"] != nil && !haveRetArg && len(retLine) > 0 { if fn != nil && fn.vars["ret"] != nil && !haveRetArg && len(retLine) > 0 {
v := fn.vars["ret"] v := fn.vars["ret"]
resultStr := fmt.Sprintf("%d-byte ret+%d(FP)", v.size, v.off)
if abi == "ABIInternal" {
resultStr = "result register"
}
for _, line := range retLine { for _, line := range retLine {
pass.Reportf(analysisutil.LineStart(tf, line), "[%s] %s: RET without writing to %d-byte ret+%d(FP)", arch, fnName, v.size, v.off) pass.Reportf(analysisutil.LineStart(tf, line), "[%s] %s: RET without writing to %s", arch, fnName, resultStr)
} }
} }
retLine = nil retLine = nil
} }
trimABI := func(fnName string) string { trimABI := func(fnName string) (string, string) {
m := abiSuff.FindStringSubmatch(fnName) m := abiSuff.FindStringSubmatch(fnName)
if m != nil { if m != nil {
return m[1] return m[1], m[2]
} }
return fnName return fnName, ""
} }
for lineno, line := range lines { for lineno, line := range lines {
lineno++ lineno++
@ -273,11 +283,12 @@ Files:
// log.Printf("%s:%d: [%s] cannot check cross-package assembly function: %s is in package %s", fname, lineno, arch, fnName, pkgPath) // log.Printf("%s:%d: [%s] cannot check cross-package assembly function: %s is in package %s", fname, lineno, arch, fnName, pkgPath)
fn = nil fn = nil
fnName = "" fnName = ""
abi = ""
continue continue
} }
} }
// Trim off optional ABI selector. // Trim off optional ABI selector.
fnName := trimABI(fnName) fnName, abi = trimABI(fnName)
flag := m[3] flag := m[3]
fn = knownFunc[fnName][arch] fn = knownFunc[fnName][arch]
if fn != nil { if fn != nil {
@ -305,6 +316,7 @@ Files:
flushRet() flushRet()
fn = nil fn = nil
fnName = "" fnName = ""
abi = ""
continue continue
} }
@ -335,6 +347,15 @@ Files:
haveRetArg = true haveRetArg = true
} }
if abi == "ABIInternal" && !haveRetArg {
for _, reg := range archDef.retRegs {
if strings.Contains(line, reg) {
haveRetArg = true
break
}
}
}
for _, m := range asmSP.FindAllStringSubmatch(line, -1) { for _, m := range asmSP.FindAllStringSubmatch(line, -1) {
if m[3] != archDef.stack || wroteSP || noframe { if m[3] != archDef.stack || wroteSP || noframe {
continue continue

View file

@ -52,4 +52,7 @@ func pickStableABI(x int)
func pickInternalABI(x int) func pickInternalABI(x int)
func pickFutureABI(x int) func pickFutureABI(x int)
func returnABIInternal() int
func returnmissingABIInternal() int
func retjmp() int func retjmp() int

View file

@ -346,6 +346,14 @@ TEXT ·pickFutureABI<ABISomethingNotyetInvented>(SB), NOSPLIT, $32
MOVQ x+0(FP), AX MOVQ x+0(FP), AX
RET RET
// writing to result in ABIInternal function
TEXT ·returnABIInternal<ABIInternal>(SB), NOSPLIT, $32
MOVQ $123, AX
RET
TEXT ·returnmissingABIInternal<ABIInternal>(SB), NOSPLIT, $32
MOVQ $123, CX
RET // want `RET without writing to result register`
// return jump // return jump
TEXT ·retjmp(SB), NOSPLIT, $0-8 TEXT ·retjmp(SB), NOSPLIT, $0-8
RET retjmp1(SB) // It's okay to not write results if there's a tail call. RET retjmp1(SB) // It's okay to not write results if there's a tail call.