diff options
author | Philip Hofer <phofer@umich.edu> | 2017-03-03 10:57:53 -0800 |
---|---|---|
committer | David Chase <drchase@google.com> | 2017-03-13 18:24:57 +0000 |
commit | 4e0c7c3f61475116c4ae8d11ef796819d9c404f0 (patch) | |
tree | 62b5e1615ca8dcfd06658b6f2f955141abd4a396 /test/devirt.go | |
parent | 26e726c3092264584053a4f81714dcc8c91d2153 (diff) | |
download | go-4e0c7c3f61475116c4ae8d11ef796819d9c404f0.tar.gz go-4e0c7c3f61475116c4ae8d11ef796819d9c404f0.zip |
cmd/compile: de-virtualize interface calls
With this change, code like
h := sha1.New()
h.Write(buf)
sum := h.Sum()
gets compiled into static calls rather than
interface calls, because the compiler is able
to prove that 'h' is really a *sha1.digest.
The InterCall re-write rule hits a few dozen times
during make.bash, and hundreds of times during all.bash.
The most common pattern identified by the compiler
is a constructor like
func New() Interface { return &impl{...} }
where the constructor gets inlined into the caller,
and the result is used immediately. Examples include
{sha1,md5,crc32,crc64,...}.New, base64.NewEncoder,
base64.NewDecoder, errors.New, net.Pipe, and so on.
Some existing benchmarks that change on darwin/amd64:
Crc64/ISO4KB-8 2.67µs ± 1% 2.66µs ± 0% -0.36% (p=0.015 n=10+10)
Crc64/ISO1KB-8 694ns ± 0% 690ns ± 1% -0.59% (p=0.001 n=10+10)
Adler32KB-8 473ns ± 1% 471ns ± 0% -0.39% (p=0.010 n=10+9)
On architectures like amd64, the reduction in code size
appears to contribute more to benchmark improvements than just
removing the indirect call, since that branch gets predicted
accurately when called in a loop.
Updates #19361
Change-Id: Ia9d30afdd5f6b4d38d38b14b88f308acae8ce7ed
Reviewed-on: https://go-review.googlesource.com/37751
Run-TryBot: Philip Hofer <phofer@umich.edu>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
Diffstat (limited to 'test/devirt.go')
-rw-r--r-- | test/devirt.go | 64 |
1 files changed, 64 insertions, 0 deletions
diff --git a/test/devirt.go b/test/devirt.go new file mode 100644 index 0000000000..a2211f185c --- /dev/null +++ b/test/devirt.go @@ -0,0 +1,64 @@ +// errorcheck -0 -d=ssa/opt/debug=3 + +package main + +import ( + "crypto/sha1" + "errors" + "fmt" + "sync" +) + +func f0() { + v := errors.New("error string") + _ = v.Error() // ERROR "de-virtualizing call$" +} + +func f1() { + h := sha1.New() + buf := make([]byte, 4) + h.Write(buf) // ERROR "de-virtualizing call$" + _ = h.Sum(nil) // ERROR "de-virtualizing call$" +} + +func f2() { + // trickier case: make sure we see this is *sync.rlocker + // instead of *sync.RWMutex, + // even though they are the same pointers + var m sync.RWMutex + r := m.RLocker() + + // deadlock if the type of 'r' is improperly interpreted + // as *sync.RWMutex + r.Lock() // ERROR "de-virtualizing call$" + m.RLock() + r.Unlock() // ERROR "de-virtualizing call$" + m.RUnlock() +} + +type multiword struct{ a, b, c int } + +func (m multiword) Error() string { return fmt.Sprintf("%d, %d, %d", m.a, m.b, m.c) } + +func f3() { + // can't de-virtualize this one yet; + // it passes through a call to iconvT2I + var err error + err = multiword{1, 2, 3} + if err.Error() != "1, 2, 3" { + panic("bad call") + } + + // ... but we can do this one + err = &multiword{1, 2, 3} + if err.Error() != "1, 2, 3" { // ERROR "de-virtualizing call$" + panic("bad call") + } +} + +func main() { + f0() + f1() + f2() + f3() +} |