diff options
author | philhofer <phofer@umich.edu> | 2017-03-13 15:03:17 -0700 |
---|---|---|
committer | David Chase <drchase@google.com> | 2017-03-14 18:49:23 +0000 |
commit | 295307ae78f8dd463a2ab8d85a1592ca76619d36 (patch) | |
tree | f6222675f59f884597f0684b3249937ec9a0e1e5 /src/cmd/compile/internal/ssa | |
parent | 691755304ce49887f65f8f84e18cda3814b46e5c (diff) | |
download | go-295307ae78f8dd463a2ab8d85a1592ca76619d36.tar.gz go-295307ae78f8dd463a2ab8d85a1592ca76619d36.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: I57d4dc21ef40a05ec0fbd55a9bb0eb74cdc67a3d
Reviewed-on: https://go-review.googlesource.com/38139
Run-TryBot: Philip Hofer <phofer@umich.edu>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: David Chase <drchase@google.com>
Diffstat (limited to 'src/cmd/compile/internal/ssa')
-rw-r--r-- | src/cmd/compile/internal/ssa/config.go | 6 | ||||
-rw-r--r-- | src/cmd/compile/internal/ssa/export_test.go | 31 | ||||
-rw-r--r-- | src/cmd/compile/internal/ssa/gen/generic.rules | 7 | ||||
-rw-r--r-- | src/cmd/compile/internal/ssa/rewrite.go | 20 | ||||
-rw-r--r-- | src/cmd/compile/internal/ssa/rewritegeneric.go | 48 |
5 files changed, 97 insertions, 15 deletions
diff --git a/src/cmd/compile/internal/ssa/config.go b/src/cmd/compile/internal/ssa/config.go index ea03ed7f10..52692847b9 100644 --- a/src/cmd/compile/internal/ssa/config.go +++ b/src/cmd/compile/internal/ssa/config.go @@ -121,6 +121,12 @@ type Frontend interface { SplitArray(LocalSlot) LocalSlot // array must be length 1 SplitInt64(LocalSlot) (LocalSlot, LocalSlot) // returns (hi, lo) + // DerefItab dereferences an itab function + // entry, given the symbol of the itab and + // the byte offset of the function pointer. + // It may return nil. + DerefItab(sym *obj.LSym, offset int64) *obj.LSym + // Line returns a string describing the given position. Line(src.XPos) string diff --git a/src/cmd/compile/internal/ssa/export_test.go b/src/cmd/compile/internal/ssa/export_test.go index 74bb08d5c2..b687076a28 100644 --- a/src/cmd/compile/internal/ssa/export_test.go +++ b/src/cmd/compile/internal/ssa/export_test.go @@ -97,21 +97,22 @@ func (d DummyFrontend) Warnl(_ src.XPos, msg string, args ...interface{}) { d.t func (d DummyFrontend) Debug_checknil() bool { return false } func (d DummyFrontend) Debug_wb() bool { return false } -func (d DummyFrontend) TypeBool() Type { return TypeBool } -func (d DummyFrontend) TypeInt8() Type { return TypeInt8 } -func (d DummyFrontend) TypeInt16() Type { return TypeInt16 } -func (d DummyFrontend) TypeInt32() Type { return TypeInt32 } -func (d DummyFrontend) TypeInt64() Type { return TypeInt64 } -func (d DummyFrontend) TypeUInt8() Type { return TypeUInt8 } -func (d DummyFrontend) TypeUInt16() Type { return TypeUInt16 } -func (d DummyFrontend) TypeUInt32() Type { return TypeUInt32 } -func (d DummyFrontend) TypeUInt64() Type { return TypeUInt64 } -func (d DummyFrontend) TypeFloat32() Type { return TypeFloat32 } -func (d DummyFrontend) TypeFloat64() Type { return TypeFloat64 } -func (d DummyFrontend) TypeInt() Type { return TypeInt64 } -func (d DummyFrontend) TypeUintptr() Type { return TypeUInt64 } -func (d DummyFrontend) TypeString() Type { panic("unimplemented") } -func (d DummyFrontend) TypeBytePtr() Type { return TypeBytePtr } +func (d DummyFrontend) TypeBool() Type { return TypeBool } +func (d DummyFrontend) TypeInt8() Type { return TypeInt8 } +func (d DummyFrontend) TypeInt16() Type { return TypeInt16 } +func (d DummyFrontend) TypeInt32() Type { return TypeInt32 } +func (d DummyFrontend) TypeInt64() Type { return TypeInt64 } +func (d DummyFrontend) TypeUInt8() Type { return TypeUInt8 } +func (d DummyFrontend) TypeUInt16() Type { return TypeUInt16 } +func (d DummyFrontend) TypeUInt32() Type { return TypeUInt32 } +func (d DummyFrontend) TypeUInt64() Type { return TypeUInt64 } +func (d DummyFrontend) TypeFloat32() Type { return TypeFloat32 } +func (d DummyFrontend) TypeFloat64() Type { return TypeFloat64 } +func (d DummyFrontend) TypeInt() Type { return TypeInt64 } +func (d DummyFrontend) TypeUintptr() Type { return TypeUInt64 } +func (d DummyFrontend) TypeString() Type { panic("unimplemented") } +func (d DummyFrontend) TypeBytePtr() Type { return TypeBytePtr } +func (d DummyFrontend) DerefItab(sym *obj.LSym, off int64) *obj.LSym { return nil } func (d DummyFrontend) CanSSA(t Type) bool { // There are no un-SSAable types in dummy land. diff --git a/src/cmd/compile/internal/ssa/gen/generic.rules b/src/cmd/compile/internal/ssa/gen/generic.rules index 53f0490c4c..73341a52d7 100644 --- a/src/cmd/compile/internal/ssa/gen/generic.rules +++ b/src/cmd/compile/internal/ssa/gen/generic.rules @@ -1431,3 +1431,10 @@ && c == config.ctxt.FixedFrameSize() + config.RegSize // offset of return value && warnRule(config.Debug_checknil() && v.Pos.Line() > 1, v, "removed nil check") -> (Invalid) + +// De-virtualize interface calls into static calls. +// Note that (ITab (IMake)) doesn't get +// rewritten until after the first opt pass, +// so this rule should trigger reliably. +(InterCall [argsize] (Load (OffPtr [off] (ITab (IMake (Addr {itab} (SB)) _))) _) mem) && devirt(v, itab, off) != nil -> + (StaticCall [argsize] {devirt(v, itab, off)} mem) diff --git a/src/cmd/compile/internal/ssa/rewrite.go b/src/cmd/compile/internal/ssa/rewrite.go index 9c9c6b5ecc..26c6518eeb 100644 --- a/src/cmd/compile/internal/ssa/rewrite.go +++ b/src/cmd/compile/internal/ssa/rewrite.go @@ -5,6 +5,7 @@ package ssa import ( + "cmd/internal/obj" "crypto/sha1" "fmt" "math" @@ -384,6 +385,25 @@ func uaddOvf(a, b int64) bool { return uint64(a)+uint64(b) < uint64(a) } +// de-virtualize an InterCall +// 'sym' is the symbol for the itab +func devirt(v *Value, sym interface{}, offset int64) *obj.LSym { + f := v.Block.Func + ext, ok := sym.(*ExternSymbol) + if !ok { + return nil + } + lsym := f.Config.Frontend().DerefItab(ext.Sym, offset) + if f.pass.debug > 0 { + if lsym != nil { + f.Config.Warnl(v.Pos, "de-virtualizing call") + } else { + f.Config.Warnl(v.Pos, "couldn't de-virtualize call") + } + } + return lsym +} + // isSamePtr reports whether p1 and p2 point to the same address. func isSamePtr(p1, p2 *Value) bool { if p1 == p2 { diff --git a/src/cmd/compile/internal/ssa/rewritegeneric.go b/src/cmd/compile/internal/ssa/rewritegeneric.go index 10a4a4383c..b998266f2b 100644 --- a/src/cmd/compile/internal/ssa/rewritegeneric.go +++ b/src/cmd/compile/internal/ssa/rewritegeneric.go @@ -124,6 +124,8 @@ func rewriteValuegeneric(v *Value, config *Config) bool { return rewriteValuegeneric_OpGreater8U(v, config) case OpIMake: return rewriteValuegeneric_OpIMake(v, config) + case OpInterCall: + return rewriteValuegeneric_OpInterCall(v, config) case OpIsInBounds: return rewriteValuegeneric_OpIsInBounds(v, config) case OpIsNonNil: @@ -5736,6 +5738,52 @@ func rewriteValuegeneric_OpIMake(v *Value, config *Config) bool { } return false } +func rewriteValuegeneric_OpInterCall(v *Value, config *Config) bool { + b := v.Block + _ = b + // match: (InterCall [argsize] (Load (OffPtr [off] (ITab (IMake (Addr {itab} (SB)) _))) _) mem) + // cond: devirt(v, itab, off) != nil + // result: (StaticCall [argsize] {devirt(v, itab, off)} mem) + for { + argsize := v.AuxInt + v_0 := v.Args[0] + if v_0.Op != OpLoad { + break + } + v_0_0 := v_0.Args[0] + if v_0_0.Op != OpOffPtr { + break + } + off := v_0_0.AuxInt + v_0_0_0 := v_0_0.Args[0] + if v_0_0_0.Op != OpITab { + break + } + v_0_0_0_0 := v_0_0_0.Args[0] + if v_0_0_0_0.Op != OpIMake { + break + } + v_0_0_0_0_0 := v_0_0_0_0.Args[0] + if v_0_0_0_0_0.Op != OpAddr { + break + } + itab := v_0_0_0_0_0.Aux + v_0_0_0_0_0_0 := v_0_0_0_0_0.Args[0] + if v_0_0_0_0_0_0.Op != OpSB { + break + } + mem := v.Args[1] + if !(devirt(v, itab, off) != nil) { + break + } + v.reset(OpStaticCall) + v.AuxInt = argsize + v.Aux = devirt(v, itab, off) + v.AddArg(mem) + return true + } + return false +} func rewriteValuegeneric_OpIsInBounds(v *Value, config *Config) bool { b := v.Block _ = b |