Skip to content

Commit 745ee95

Browse files
committed
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 golang#19361 Change-Id: Ia9d30afdd5f6b4d38d38b14b88f308acae8ce7ed
1 parent 6491496 commit 745ee95

File tree

13 files changed

+309
-39
lines changed

13 files changed

+309
-39
lines changed

src/cmd/compile/internal/gc/reflect.go

Lines changed: 92 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ import (
1616
type itabEntry struct {
1717
t, itype *Type
1818
sym *Sym
19+
lsym *obj.LSym
20+
21+
// symbols of each method in
22+
// the itab, sorted by byte offset
23+
entries []*obj.LSym
1924
}
2025

2126
type ptabEntry struct {
@@ -78,7 +83,7 @@ const (
7883
func structfieldSize() int { return 3 * Widthptr } // Sizeof(runtime.structfield{})
7984
func imethodSize() int { return 4 + 4 } // Sizeof(runtime.imethod{})
8085
func uncommonSize(t *Type) int { // Sizeof(runtime.uncommontype{})
81-
if t.Sym == nil && len(methods(t)) == 0 {
86+
if t.Sym == nil && len(methods(t, true)) == 0 {
8287
return 0
8388
}
8489
return 4 + 2 + 2 + 4 + 4
@@ -287,8 +292,8 @@ func methodfunc(f *Type, receiver *Type) *Type {
287292
}
288293

289294
// methods returns the methods of the non-interface type t, sorted by name.
290-
// Generates stub functions as needed.
291-
func methods(t *Type) []*Sig {
295+
// If gen is true, generates stub functions as needed.
296+
func methods(t *Type, gen bool) []*Sig {
292297
// method type
293298
mt := methtype(t)
294299

@@ -352,7 +357,7 @@ func methods(t *Type) []*Sig {
352357
sig.type_ = methodfunc(f.Type, t)
353358
sig.mtype = methodfunc(f.Type, nil)
354359

355-
if !sig.isym.Siggen() {
360+
if gen && !sig.isym.Siggen() {
356361
sig.isym.SetSiggen(true)
357362
if !eqtype(this, it) || this.Width < Types[Tptr].Width {
358363
compiling_wrappers = 1
@@ -361,7 +366,7 @@ func methods(t *Type) []*Sig {
361366
}
362367
}
363368

364-
if !sig.tsym.Siggen() {
369+
if gen && !sig.tsym.Siggen() {
365370
sig.tsym.SetSiggen(true)
366371
if !eqtype(this, t) {
367372
compiling_wrappers = 1
@@ -376,7 +381,8 @@ func methods(t *Type) []*Sig {
376381
}
377382

378383
// imethods returns the methods of the interface type t, sorted by name.
379-
func imethods(t *Type) []*Sig {
384+
// If gen is false, then wrappers are not eagerly generated.
385+
func imethods(t *Type, gen bool) []*Sig {
380386
var methods []*Sig
381387
for _, f := range t.Fields().Slice() {
382388
if f.Type.Etype != TFUNC || f.Sym == nil {
@@ -416,7 +422,7 @@ func imethods(t *Type) []*Sig {
416422
// code can refer to it.
417423
isym := methodsym(method, t, 0)
418424

419-
if !isym.Siggen() {
425+
if gen && !isym.Siggen() {
420426
isym.SetSiggen(true)
421427
genwrapper(t, f, isym, 0)
422428
}
@@ -602,7 +608,7 @@ func dname(name, tag string, pkg *Pkg, exported bool) *obj.LSym {
602608
// dataAdd is the offset in bytes after the header where the
603609
// backing array of the []method field is written (by dextratypeData).
604610
func dextratype(s *Sym, ot int, t *Type, dataAdd int) int {
605-
m := methods(t)
611+
m := methods(t, true)
606612
if t.Sym == nil && len(m) == 0 {
607613
return ot
608614
}
@@ -653,7 +659,7 @@ func typePkg(t *Type) *Pkg {
653659
// runtime.uncommontype.
654660
func dextratypeData(s *Sym, ot int, t *Type) int {
655661
lsym := Linksym(s)
656-
for _, a := range methods(t) {
662+
for _, a := range methods(t, true) {
657663
// ../../../../runtime/type.go:/method
658664
exported := exportname(a.name)
659665
var pkg *Pkg
@@ -838,7 +844,7 @@ func dcommontype(s *Sym, ot int, t *Type) int {
838844
var sptr *Sym
839845
if !t.IsPtr() || t.ptrTo != nil {
840846
tptr := ptrto(t)
841-
if t.Sym != nil || methods(tptr) != nil {
847+
if t.Sym != nil || methods(tptr, true) != nil {
842848
sptrWeak = false
843849
}
844850
sptr = dtypesym(tptr)
@@ -992,7 +998,7 @@ func typename(t *Type) *Node {
992998
return n
993999
}
9941000

995-
func itabname(t, itype *Type) *Node {
1001+
func itabname(t, itype *Type, genimpl bool) *Node {
9961002
if t == nil || (t.IsPtr() && t.Elem() == nil) || t.IsUntyped() || !itype.IsInterface() || itype.IsEmptyInterface() {
9971003
Fatalf("itabname(%v, %v)", t, itype)
9981004
}
@@ -1004,7 +1010,18 @@ func itabname(t, itype *Type) *Node {
10041010
n.Typecheck = 1
10051011
s.Def = n
10061012

1007-
itabs = append(itabs, itabEntry{t: t, itype: itype, sym: s})
1013+
var methods []*obj.LSym
1014+
if genimpl {
1015+
methods = genfun(t, itype)
1016+
}
1017+
1018+
itabs = append(itabs, itabEntry{
1019+
t: t,
1020+
itype: itype,
1021+
sym: s,
1022+
lsym: Linksym(s),
1023+
entries: methods,
1024+
})
10081025
}
10091026

10101027
n := nod(OADDR, s.Def, nil)
@@ -1224,7 +1241,7 @@ ok:
12241241
}
12251242

12261243
case TINTER:
1227-
m := imethods(t)
1244+
m := imethods(t, true)
12281245
n := len(m)
12291246
for _, a := range m {
12301247
dtypesym(a.type_)
@@ -1379,6 +1396,64 @@ ok:
13791396
return s
13801397
}
13811398

1399+
// calculate the set of concrete implementations
1400+
// for each entry in the itab
1401+
// given by the type/interface pair
1402+
func genfun(t, it *Type) []*obj.LSym {
1403+
if t == nil || it == nil {
1404+
return nil
1405+
}
1406+
sigs := imethods(it, false)
1407+
methods := methods(t, false)
1408+
out := make([]*obj.LSym, 0, len(sigs))
1409+
if len(sigs) == 0 {
1410+
return nil
1411+
}
1412+
1413+
// both sigs and methods are sorted by name,
1414+
// so we can find the intersect in a single pass
1415+
for _, m := range methods {
1416+
if m.name == sigs[0].name {
1417+
out = append(out, Linksym(m.isym))
1418+
sigs = sigs[1:]
1419+
if len(sigs) == 0 {
1420+
break
1421+
}
1422+
}
1423+
}
1424+
1425+
return out
1426+
}
1427+
1428+
// itabsym is used by the SSA backend
1429+
// to convert interface calls into static calls;
1430+
// it deliberately doesn't create any additional Nodes, Syms, etc.
1431+
func itabsym(it *obj.LSym, offset int64) *obj.LSym {
1432+
var syms []*obj.LSym
1433+
1434+
// TODO: something that isn't O(itabs)?
1435+
// We expect this lookup to occur
1436+
// infrequently in practice; perhaps
1437+
// this is offset by O(1) itab creation.
1438+
for i := range itabs {
1439+
e := &itabs[i]
1440+
if e.lsym == it {
1441+
syms = e.entries
1442+
break
1443+
}
1444+
}
1445+
if syms == nil {
1446+
return nil
1447+
}
1448+
1449+
// keep this arithmetic in sync with *itab layout
1450+
methodnum := int((offset - 3*int64(Widthptr) - 8) / int64(Widthptr))
1451+
if methodnum >= len(syms) {
1452+
return nil
1453+
}
1454+
return syms[methodnum]
1455+
}
1456+
13821457
func dumptypestructs() {
13831458
// copy types from externdcl list to signatlist
13841459
for _, n := range externdcl {
@@ -1417,10 +1492,10 @@ func dumptypestructs() {
14171492
// }
14181493
o := dsymptr(i.sym, 0, dtypesym(i.itype), 0)
14191494
o = dsymptr(i.sym, o, dtypesym(i.t), 0)
1420-
o += Widthptr // skip link field
1421-
o = duint32(i.sym, o, typehash(i.t)) // copy of type hash
1422-
o += 4 // skip bad/inhash/unused fields
1423-
o += len(imethods(i.itype)) * Widthptr // skip fun method pointers
1495+
o += Widthptr // skip link field
1496+
o = duint32(i.sym, o, typehash(i.t)) // copy of type hash
1497+
o += 4 // skip bad/inhash/unused fields
1498+
o += len(imethods(i.itype, true)) * Widthptr // skip fun method pointers
14241499
// at runtime the itab will contain pointers to types, other itabs and
14251500
// method functions. None are allocated on heap, so we can use obj.NOPTR.
14261501
ggloblsym(i.sym, int32(o), int16(obj.DUPOK|obj.NOPTR))

src/cmd/compile/internal/gc/sinit.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -502,7 +502,7 @@ func staticassign(l *Node, r *Node, out *[]*Node) bool {
502502
if l.Type.IsEmptyInterface() {
503503
itab = typename(val.Type)
504504
} else {
505-
itab = itabname(val.Type, l.Type)
505+
itab = itabname(val.Type, l.Type, true)
506506
}
507507

508508
// Create a copy of l to modify while we emit data.

src/cmd/compile/internal/gc/ssa.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4090,7 +4090,10 @@ func (s *state) dottype(n *Node, commaok bool) (res, resok *ssa.Value) {
40904090
targetITab = target
40914091
} else {
40924092
// Looking for pointer to itab for target type and source interface.
4093-
targetITab = s.expr(itabname(n.Type, n.Left.Type))
4093+
// Don't generate the implementation functions; there's no
4094+
// de-virtualization opportunity here, and we're already
4095+
// compiling a function.
4096+
targetITab = s.expr(itabname(n.Type, n.Left.Type, false))
40944097
}
40954098

40964099
var tmp *Node // temporary for use with large types
@@ -4884,6 +4887,10 @@ func (e *ssaExport) SplitArray(name ssa.LocalSlot) ssa.LocalSlot {
48844887
return ssa.LocalSlot{N: n, Type: et, Off: name.Off}
48854888
}
48864889

4890+
func (e *ssaExport) DerefItab(it *obj.LSym, offset int64) *obj.LSym {
4891+
return itabsym(it, offset)
4892+
}
4893+
48874894
// namedAuto returns a new AUTO variable with the given name and type.
48884895
// These are exposed to the debugger.
48894896
func (e *ssaExport) namedAuto(name string, typ ssa.Type) ssa.GCNode {

src/cmd/compile/internal/gc/walk.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,10 @@ func isSmallMakeSlice(n *Node) bool {
386386
func walkexprlist(s []*Node, init *Nodes) {
387387
for i := range s {
388388
s[i] = walkexpr(s[i], init)
389+
if Curfn == nil {
390+
println(s[i].String())
391+
panic("curfn is nil")
392+
}
389393
}
390394
}
391395

@@ -884,7 +888,7 @@ opswitch:
884888
if n.Type.IsEmptyInterface() {
885889
t = typename(n.Left.Type)
886890
} else {
887-
t = itabname(n.Left.Type, n.Type)
891+
t = itabname(n.Left.Type, n.Type, true)
888892
}
889893
l := nod(OEFACE, t, n.Left)
890894
l.Type = n.Type
@@ -932,7 +936,7 @@ opswitch:
932936
if n.Type.IsEmptyInterface() {
933937
t = typename(n.Left.Type)
934938
} else {
935-
t = itabname(n.Left.Type, n.Type)
939+
t = itabname(n.Left.Type, n.Type, true)
936940
}
937941
l := nod(OEFACE, t, typecheck(nod(OADDR, value, nil), Erv))
938942
l.Type = n.Type
@@ -978,7 +982,7 @@ opswitch:
978982
if n.Left.Type.IsInterface() {
979983
ll = append(ll, typename(n.Type))
980984
} else {
981-
ll = append(ll, itabname(n.Left.Type, n.Type))
985+
ll = append(ll, itabname(n.Left.Type, n.Type, false))
982986
}
983987
}
984988

src/cmd/compile/internal/ssa/config.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,10 @@ type Frontend interface {
121121
SplitArray(LocalSlot) LocalSlot // array must be length 1
122122
SplitInt64(LocalSlot) (LocalSlot, LocalSlot) // returns (hi, lo)
123123

124+
// DerefItab dereferences an itab function
125+
// entry, given the byte offset of the entry.
126+
DerefItab(sym *obj.LSym, offset int64) *obj.LSym
127+
124128
// Line returns a string describing the given position.
125129
Line(src.XPos) string
126130

src/cmd/compile/internal/ssa/export_test.go

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -84,21 +84,22 @@ func (d DummyFrontend) Warnl(_ src.XPos, msg string, args ...interface{}) { d.t
8484
func (d DummyFrontend) Debug_checknil() bool { return false }
8585
func (d DummyFrontend) Debug_wb() bool { return false }
8686

87-
func (d DummyFrontend) TypeBool() Type { return TypeBool }
88-
func (d DummyFrontend) TypeInt8() Type { return TypeInt8 }
89-
func (d DummyFrontend) TypeInt16() Type { return TypeInt16 }
90-
func (d DummyFrontend) TypeInt32() Type { return TypeInt32 }
91-
func (d DummyFrontend) TypeInt64() Type { return TypeInt64 }
92-
func (d DummyFrontend) TypeUInt8() Type { return TypeUInt8 }
93-
func (d DummyFrontend) TypeUInt16() Type { return TypeUInt16 }
94-
func (d DummyFrontend) TypeUInt32() Type { return TypeUInt32 }
95-
func (d DummyFrontend) TypeUInt64() Type { return TypeUInt64 }
96-
func (d DummyFrontend) TypeFloat32() Type { return TypeFloat32 }
97-
func (d DummyFrontend) TypeFloat64() Type { return TypeFloat64 }
98-
func (d DummyFrontend) TypeInt() Type { return TypeInt64 }
99-
func (d DummyFrontend) TypeUintptr() Type { return TypeUInt64 }
100-
func (d DummyFrontend) TypeString() Type { panic("unimplemented") }
101-
func (d DummyFrontend) TypeBytePtr() Type { return TypeBytePtr }
87+
func (d DummyFrontend) TypeBool() Type { return TypeBool }
88+
func (d DummyFrontend) TypeInt8() Type { return TypeInt8 }
89+
func (d DummyFrontend) TypeInt16() Type { return TypeInt16 }
90+
func (d DummyFrontend) TypeInt32() Type { return TypeInt32 }
91+
func (d DummyFrontend) TypeInt64() Type { return TypeInt64 }
92+
func (d DummyFrontend) TypeUInt8() Type { return TypeUInt8 }
93+
func (d DummyFrontend) TypeUInt16() Type { return TypeUInt16 }
94+
func (d DummyFrontend) TypeUInt32() Type { return TypeUInt32 }
95+
func (d DummyFrontend) TypeUInt64() Type { return TypeUInt64 }
96+
func (d DummyFrontend) TypeFloat32() Type { return TypeFloat32 }
97+
func (d DummyFrontend) TypeFloat64() Type { return TypeFloat64 }
98+
func (d DummyFrontend) TypeInt() Type { return TypeInt64 }
99+
func (d DummyFrontend) TypeUintptr() Type { return TypeUInt64 }
100+
func (d DummyFrontend) TypeString() Type { panic("unimplemented") }
101+
func (d DummyFrontend) TypeBytePtr() Type { return TypeBytePtr }
102+
func (d DummyFrontend) DerefItab(sym *obj.LSym, off int64) *obj.LSym { return nil }
102103

103104
func (d DummyFrontend) CanSSA(t Type) bool {
104105
// There are no un-SSAable types in dummy land.

src/cmd/compile/internal/ssa/gen/generic.rules

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1430,3 +1430,7 @@
14301430
&& c == config.ctxt.FixedFrameSize() + config.RegSize // offset of return value
14311431
&& warnRule(config.Debug_checknil() && v.Pos.Line() > 1, v, "removed nil check")
14321432
-> (Invalid)
1433+
1434+
// De-virtualize interface calls
1435+
(InterCall [argsize] (Load (OffPtr [off] (ITab (IMake (Addr {itab} (SB)) z))) _) mem) && devirt(v, itab, off) != nil ->
1436+
(StaticCall [argsize] {devirt(v, itab, off)} cleanIntercall(mem, z))

src/cmd/compile/internal/ssa/gen/rulegen.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -664,7 +664,7 @@ func parseValue(val string, arch arch, loc string) (op opData, oparch string, ty
664664
}
665665
if aux != "" {
666666
switch op.aux {
667-
case "String", "Sym", "SymOff", "SymValAndOff", "SymInt32":
667+
case "String", "Sym", "SymOff", "SymValAndOff", "SymInt32", "Typ":
668668
default:
669669
log.Fatalf("%s: op %s %s can't have aux", loc, op.name, op.aux)
670670
}

0 commit comments

Comments
 (0)