go/test/devirt.go
Philip Hofer 4e0c7c3f61 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>
2017-03-13 18:24:57 +00:00

65 lines
1.2 KiB
Go

// 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()
}