go: cmd/compile: -m output is missing escape information
In #32670 we are considering a new API for golang.org/x/crypto/curve25519. One of the changes I’d like to introduce is not to require the caller to pre-allocate the destination. OTOH, I’d like not to introduce a heap allocation in the process.
I was hoping to be able to use the inliner to inline the variable declaration in the caller, where it has a chance not to escape. I used -gcflags -m
to verify if it would work.
package main
import (
"crypto/subtle"
"errors"
"golang.org/x/crypto/curve25519"
)
func main() {
scalar, point := make([]byte, 32), make([]byte, 32)
res, err := X25519(scalar, point)
if err != nil {
panic(err)
}
println(res)
}
func X25519(scalar, point []byte) ([]byte, error) {
var dst [32]byte
return x25519(&dst, scalar, point)
}
func x25519(dst *[32]byte, scalar, point []byte) ([]byte, error) {
var in, base, zero [32]byte
copy(in[:], scalar)
copy(base[:], point)
curve25519.ScalarMult(dst, &in, &base)
if subtle.ConstantTimeCompare(dst[:], zero[:]) == 1 {
return nil, errors.New("bad input")
}
return dst[:], nil
}
In Go 1.12.6 this works as intended. Note main &dst does not escape
.
# play
./inline.go:28:23: inlining call to curve25519.ScalarMult
./inline.go:30:25: inlining call to errors.New
./inline.go:19:6: can inline X25519
./inline.go:12:20: inlining call to X25519
/var/folders/df/mrk3bfz149n8zb5h5p1vp_1m00hbbm/T/go-build156101582/b001/_gomod_.go:6:6: can inline init.0
./inline.go:30:25: error(&errors.errorString literal) escapes to heap
./inline.go:30:25: &errors.errorString literal escapes to heap
./inline.go:24:13: leaking param: dst to result ~r3 level=0
./inline.go:24:28: x25519 scalar does not escape
./inline.go:24:36: x25519 point does not escape
./inline.go:26:9: x25519 in does not escape
./inline.go:27:11: x25519 base does not escape
./inline.go:28:29: x25519 &in does not escape
./inline.go:28:34: x25519 &base does not escape
./inline.go:29:44: x25519 zero does not escape
./inline.go:11:23: main make([]byte, 32) does not escape
./inline.go:11:41: main make([]byte, 32) does not escape
./inline.go:12:20: main &dst does not escape
./inline.go:21:16: &dst escapes to heap
./inline.go:20:6: moved to heap: dst
./inline.go:19:13: X25519 scalar does not escape
./inline.go:19:21: X25519 point does not escape
In Go +2f387ac1f3, however, a lot of information is missing from the output. Note that the &dst escapes to heap
and main &dst does not escape
lines are gone, along with others.
# play
./inline.go:28:23: inlining call to curve25519.ScalarMult
./inline.go:30:25: inlining call to errors.New
./inline.go:19:6: can inline X25519
./inline.go:12:20: inlining call to X25519
./inline.go:24:13: leaking param: dst to result ~r3 level=0
./inline.go:24:28: x25519 scalar does not escape
./inline.go:24:36: x25519 point does not escape
./inline.go:30:25: error(&errors.errorString literal) escapes to heap
./inline.go:30:25: &errors.errorString literal escapes to heap
./inline.go:11:23: main make([]byte, 32) does not escape
./inline.go:11:41: main make([]byte, 32) does not escape
./inline.go:19:13: X25519 scalar does not escape
./inline.go:19:21: X25519 point does not escape
./inline.go:20:6: moved to heap: dst
Looking at the SSA it seems the inlined value still doesn’t escape, but it’s impossible to tell from the -gcflags -m
output now.
Marking as release-blocker to look into as a regression.
/cc @mdempsky
About this issue
- Original URL
- State: open
- Created 5 years ago
- Comments: 17 (10 by maintainers)
Yes, this is correct.
But I wanted to say that I’m a little concerned that an API design relies on inlining. The inliner works heuristically, which changes over time, and it makes no guarantee about what is inlined (although we try to avoid regression in performance). Other implementations of the compiler (e.g. gccgo) may have very different inlining model. So I’m not sure it is best to have the API tied to the implementation detail of a particular compiler, or even a particular version of it.
I think this is intentional. Some old messages are either redundant or misleading. As in the example, the presence of both
are confusing – does it escape or not? (What it actually meant is that &dst does not escape at line 12, but does escape at line 21.) Also,
./inline.go:20:6: moved to heap: dst
tells that it escapes, so theescapes to heap
message isn’t really necessary. Also, it is sometimes hard for the new escape analysis to match the exact message of the old one, as the implementation are quite different. So I think we decided to remove some of the messages.The information is still there:
./inline.go:20:6: moved to heap: dst
tells that it escapes. And the in the case of not escaping, the absence ofmoved to heap
would indicate that.Maybe the “fix” is to mention that in the release note? @aclements