summaryrefslogtreecommitdiff
path: root/src/cmd/compile/internal/gc/alg.go
diff options
context:
space:
mode:
authorKeith Randall <keithr@alum.mit.edu>2019-08-06 15:22:51 -0700
committerKeith Randall <khr@golang.org>2019-09-03 20:41:29 +0000
commit36f30ba289e31df033d100b2adb4eaf557f05a34 (patch)
tree17579106197e4c1d80b67cefdf9d8fdfd2ff2a2c /src/cmd/compile/internal/gc/alg.go
parent671bcb59666c37cb32b154c36aa91b29fdbf0835 (diff)
downloadgo-git-36f30ba289e31df033d100b2adb4eaf557f05a34.tar.gz
cmd/compile,runtime: generate hash functions only for types which are map keys
Right now we generate hash functions for all types, just in case they are used as map keys. That's a lot of wasted effort and binary size for types which will never be used as a map key. Instead, generate hash functions only for types that we know are map keys. Just doing that is a bit too simple, since maps with an interface type as a key might have to hash any concrete key type that implements that interface. So for that case, implement hashing of such types at runtime (instead of with generated code). It will be slower, but only for maps with interface types as keys, and maybe only a bit slower as the aeshash time probably dominates the dispatch time. Reorg where we keep the equals and hash functions. Move the hash function from the key type to the map type, saving a field in every non-map type. That leaves only one function in the alg structure, so get rid of that and just keep the equal function in the type descriptor itself. cmd/go now has 10 generated hash functions, instead of 504. Makes cmd/go 1.0% smaller. Update #6853. Speed on non-interface keys is unchanged. Speed on interface keys is ~20% slower: name old time/op new time/op delta MapInterfaceString-8 23.0ns ±21% 27.6ns ±14% +20.01% (p=0.002 n=10+10) MapInterfacePtr-8 19.4ns ±16% 23.7ns ± 7% +22.48% (p=0.000 n=10+8) Change-Id: I7c2e42292a46b5d4e288aaec4029bdbb01089263 Reviewed-on: https://go-review.googlesource.com/c/go/+/191198 Run-TryBot: Keith Randall <khr@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Martin Möhrmann <moehrmann@google.com>
Diffstat (limited to 'src/cmd/compile/internal/gc/alg.go')
-rw-r--r--src/cmd/compile/internal/gc/alg.go178
1 files changed, 164 insertions, 14 deletions
diff --git a/src/cmd/compile/internal/gc/alg.go b/src/cmd/compile/internal/gc/alg.go
index 2710e04f40..4759b60412 100644
--- a/src/cmd/compile/internal/gc/alg.go
+++ b/src/cmd/compile/internal/gc/alg.go
@@ -6,6 +6,7 @@ package gc
import (
"cmd/compile/internal/types"
+ "cmd/internal/obj"
"fmt"
)
@@ -183,10 +184,82 @@ func algtype1(t *types.Type) (AlgKind, *types.Type) {
return 0, nil
}
-// Generate a helper function to compute the hash of a value of type t.
-func genhash(sym *types.Sym, t *types.Type) {
+// genhash returns a symbol which is the closure used to compute
+// the hash of a value of type t.
+func genhash(t *types.Type) *obj.LSym {
+ switch algtype(t) {
+ default:
+ // genhash is only called for types that have equality
+ Fatalf("genhash %v", t)
+ case AMEM0:
+ return sysClosure("memhash0")
+ case AMEM8:
+ return sysClosure("memhash8")
+ case AMEM16:
+ return sysClosure("memhash16")
+ case AMEM32:
+ return sysClosure("memhash32")
+ case AMEM64:
+ return sysClosure("memhash64")
+ case AMEM128:
+ return sysClosure("memhash128")
+ case ASTRING:
+ return sysClosure("strhash")
+ case AINTER:
+ return sysClosure("interhash")
+ case ANILINTER:
+ return sysClosure("nilinterhash")
+ case AFLOAT32:
+ return sysClosure("f32hash")
+ case AFLOAT64:
+ return sysClosure("f64hash")
+ case ACPLX64:
+ return sysClosure("c64hash")
+ case ACPLX128:
+ return sysClosure("c128hash")
+ case AMEM:
+ // For other sizes of plain memory, we build a closure
+ // that calls memhash_varlen. The size of the memory is
+ // encoded in the first slot of the closure.
+ closure := typeLookup(fmt.Sprintf(".hashfunc%d", t.Width)).Linksym()
+ if len(closure.P) > 0 { // already generated
+ return closure
+ }
+ if memhashvarlen == nil {
+ memhashvarlen = sysfunc("memhash_varlen")
+ }
+ ot := 0
+ ot = dsymptr(closure, ot, memhashvarlen, 0)
+ ot = duintptr(closure, ot, uint64(t.Width)) // size encoded in closure
+ ggloblsym(closure, int32(ot), obj.DUPOK|obj.RODATA)
+ return closure
+ case ASPECIAL:
+ break
+ }
+
+ closure := typesymprefix(".hashfunc", t).Linksym()
+ if len(closure.P) > 0 { // already generated
+ return closure
+ }
+
+ // Generate hash functions for subtypes.
+ // There are cases where we might not use these hashes,
+ // but in that case they will get dead-code eliminated.
+ // (And the closure generated by genhash will also get
+ // dead-code eliminated, as we call the subtype hashers
+ // directly.)
+ switch t.Etype {
+ case types.TARRAY:
+ genhash(t.Elem())
+ case types.TSTRUCT:
+ for _, f := range t.FieldSlice() {
+ genhash(f.Type)
+ }
+ }
+
+ sym := typesymprefix(".hash", t)
if Debug['r'] != 0 {
- fmt.Printf("genhash %v %v\n", sym, t)
+ fmt.Printf("genhash %v %v %v\n", closure, sym, t)
}
lineno = autogeneratedPos // less confusing than end of input
@@ -204,13 +277,7 @@ func genhash(sym *types.Sym, t *types.Type) {
np := asNode(tfn.Type.Params().Field(0).Nname)
nh := asNode(tfn.Type.Params().Field(1).Nname)
- // genhash is only called for types that have equality but
- // cannot be handled by the standard algorithms,
- // so t must be either an array or a struct.
switch t.Etype {
- default:
- Fatalf("genhash %v", t)
-
case types.TARRAY:
// An array of pure memory would be handled by the
// standard algorithm, so the element type must not be
@@ -302,6 +369,13 @@ func genhash(sym *types.Sym, t *types.Type) {
fn.Func.SetNilCheckDisabled(true)
funccompile(fn)
+
+ // Build closure. It doesn't close over any variables, so
+ // it contains just the function pointer.
+ dsymptr(closure, 0, sym.Linksym(), 0)
+ ggloblsym(closure, int32(Widthptr), obj.DUPOK|obj.RODATA)
+
+ return closure
}
func hashfor(t *types.Type) *Node {
@@ -325,6 +399,8 @@ func hashfor(t *types.Type) *Node {
case ACPLX128:
sym = Runtimepkg.Lookup("c128hash")
default:
+ // Note: the caller of hashfor ensured that this symbol
+ // exists and has a body by calling genhash for t.
sym = typesymprefix(".hash", t)
}
@@ -340,13 +416,82 @@ func hashfor(t *types.Type) *Node {
return n
}
-// geneq generates a helper function to
-// check equality of two values of type t.
-func geneq(sym *types.Sym, t *types.Type) {
+// sysClosure returns a closure which will call the
+// given runtime function (with no closed-over variables).
+func sysClosure(name string) *obj.LSym {
+ s := sysvar(name + "·f")
+ if len(s.P) == 0 {
+ f := sysfunc(name)
+ dsymptr(s, 0, f, 0)
+ ggloblsym(s, int32(Widthptr), obj.DUPOK|obj.RODATA)
+ }
+ return s
+}
+
+// geneq returns a symbol which is the closure used to compute
+// equality for two objects of type t.
+func geneq(t *types.Type) *obj.LSym {
+ switch algtype(t) {
+ case ANOEQ:
+ // The runtime will panic if it tries to compare
+ // a type with a nil equality function.
+ return nil
+ case AMEM0:
+ return sysClosure("memequal0")
+ case AMEM8:
+ return sysClosure("memequal8")
+ case AMEM16:
+ return sysClosure("memequal16")
+ case AMEM32:
+ return sysClosure("memequal32")
+ case AMEM64:
+ return sysClosure("memequal64")
+ case AMEM128:
+ return sysClosure("memequal128")
+ case ASTRING:
+ return sysClosure("strequal")
+ case AINTER:
+ return sysClosure("interequal")
+ case ANILINTER:
+ return sysClosure("nilinterequal")
+ case AFLOAT32:
+ return sysClosure("f32equal")
+ case AFLOAT64:
+ return sysClosure("f64equal")
+ case ACPLX64:
+ return sysClosure("c64equal")
+ case ACPLX128:
+ return sysClosure("c128equal")
+ case AMEM:
+ // make equality closure. The size of the type
+ // is encoded in the closure.
+ closure := typeLookup(fmt.Sprintf(".eqfunc%d", t.Width)).Linksym()
+ if len(closure.P) != 0 {
+ return closure
+ }
+ if memequalvarlen == nil {
+ memequalvarlen = sysvar("memequal_varlen") // asm func
+ }
+ ot := 0
+ ot = dsymptr(closure, ot, memequalvarlen, 0)
+ ot = duintptr(closure, ot, uint64(t.Width))
+ ggloblsym(closure, int32(ot), obj.DUPOK|obj.RODATA)
+ return closure
+ case ASPECIAL:
+ break
+ }
+
+ closure := typesymprefix(".eqfunc", t).Linksym()
+ if len(closure.P) > 0 { // already generated
+ return closure
+ }
+ sym := typesymprefix(".eq", t)
if Debug['r'] != 0 {
- fmt.Printf("geneq %v %v\n", sym, t)
+ fmt.Printf("geneq %v\n", t)
}
+ // Autogenerate code for equality of structs and arrays.
+
lineno = autogeneratedPos // less confusing than end of input
dclcontext = PEXTERN
@@ -362,7 +507,7 @@ func geneq(sym *types.Sym, t *types.Type) {
np := asNode(tfn.Type.Params().Field(0).Nname)
nq := asNode(tfn.Type.Params().Field(1).Nname)
- // geneq is only called for types that have equality but
+ // We reach here only for types that have equality but
// cannot be handled by the standard algorithms,
// so t must be either an array or a struct.
switch t.Etype {
@@ -481,6 +626,11 @@ func geneq(sym *types.Sym, t *types.Type) {
// are shallow.
fn.Func.SetNilCheckDisabled(true)
funccompile(fn)
+
+ // Generate a closure which points at the function we just generated.
+ dsymptr(closure, 0, sym.Linksym(), 0)
+ ggloblsym(closure, int32(Widthptr), obj.DUPOK|obj.RODATA)
+ return closure
}
// eqfield returns the node