cmd/internal/gc: replace hash tables with Go maps

The C version of the compiler had just one hash table,
indexed by a (name string, pkg *Pkg) pair.
Because we always know the pkg during a lookup,
replace the one table with a per-Pkg map[string]*Sym.
This also lets us do non-allocating []byte key lookups.

This CL *does* change the generated object files.
In the old code, export data and init calls were emitted
in "hash table order". Now they are emitted in the order
in which they were added to the table.

Change-Id: I5a48d5c9add996dc43ad04a905641d901522de0b
Reviewed-on: https://go-review.googlesource.com/6600
Reviewed-by: Rob Pike <r@golang.org>
diff --git a/src/cmd/internal/gc/export.go b/src/cmd/internal/gc/export.go
index 54ed515..e36ea76 100644
--- a/src/cmd/internal/gc/export.go
+++ b/src/cmd/internal/gc/export.go
@@ -372,12 +372,9 @@
 	}
 	fmt.Fprintf(bout, "\n")
 
-	var p *Pkg
-	for i := int32(0); i < int32(len(phash)); i++ {
-		for p = phash[i]; p != nil; p = p.Link {
-			if p.Direct != 0 {
-				dumppkg(p)
-			}
+	for _, p := range pkgs {
+		if p.Direct != 0 {
+			dumppkg(p)
 		}
 	}
 
@@ -432,6 +429,8 @@
 	return s.Def.Type
 }
 
+var numImport = make(map[string]int)
+
 func importimport(s *Sym, path string) {
 	// Informational: record package name
 	// associated with import path, for use in
@@ -443,7 +442,7 @@
 	p := mkpkg(path)
 	if p.Name == "" {
 		p.Name = s.Name
-		Pkglookup(s.Name, nil).Npkg++
+		numImport[s.Name]++
 	} else if p.Name != s.Name {
 		Yyerror("conflicting names %s and %s for package %q", p.Name, s.Name, p.Path)
 	}
diff --git a/src/cmd/internal/gc/fmt.go b/src/cmd/internal/gc/fmt.go
index cd62cef..99aad0c 100644
--- a/src/cmd/internal/gc/fmt.go
+++ b/src/cmd/internal/gc/fmt.go
@@ -426,7 +426,7 @@
 			}
 
 			// If the name was used by multiple packages, display the full path,
-			if s.Pkg.Name != "" && Pkglookup(s.Pkg.Name, nil).Npkg > 1 {
+			if s.Pkg.Name != "" && numImport[s.Pkg.Name] > 1 {
 				return fmt.Sprintf("%q.%s", s.Pkg.Path, s.Name)
 			}
 			var fp string
diff --git a/src/cmd/internal/gc/go.go b/src/cmd/internal/gc/go.go
index 706e973..d23cdd4 100644
--- a/src/cmd/internal/gc/go.go
+++ b/src/cmd/internal/gc/go.go
@@ -110,11 +110,11 @@
 	Path     string
 	Pathsym  *Sym
 	Prefix   string
-	Link     *Pkg
 	Imported uint8
 	Exported int8
 	Direct   int8
 	Safe     bool
+	Syms     map[string]*Sym
 }
 
 type Sym struct {
@@ -122,7 +122,6 @@
 	Flags      uint8
 	Sym        uint8
 	Link       *Sym
-	Npkg       int32
 	Uniqgen    uint32
 	Importdef  *Pkg
 	Linkname   string
@@ -753,8 +752,6 @@
 
 var Debug_checknil int
 
-var hash [NHASH]*Sym
-
 var importmyname *Sym // my name for package
 
 var localpkg *Pkg // package being compiled
@@ -787,8 +784,6 @@
 
 var rawpkg *Pkg // fake package for raw symbol names
 
-var phash [128]*Pkg
-
 var Tptr int // either TPTR32 or TPTR64
 
 var myimportpath string
diff --git a/src/cmd/internal/gc/go.y b/src/cmd/internal/gc/go.y
index 96d5cbe..0961da2 100644
--- a/src/cmd/internal/gc/go.y
+++ b/src/cmd/internal/gc/go.y
@@ -249,7 +249,7 @@
 	{
 		if importpkg.Name == "" {
 			importpkg.Name = $2.Name;
-			Pkglookup($2.Name, nil).Npkg++;
+			numImport[$2.Name]++
 		} else if importpkg.Name != $2.Name {
 			Yyerror("conflicting names %s and %s for package %q", importpkg.Name, $2.Name, importpkg.Path);
 		}
diff --git a/src/cmd/internal/gc/init.go b/src/cmd/internal/gc/init.go
index 5b1dca0..c57e50b 100644
--- a/src/cmd/internal/gc/init.go
+++ b/src/cmd/internal/gc/init.go
@@ -88,14 +88,8 @@
 	}
 
 	// are there any imported init functions
-	for h := uint32(0); h < NHASH; h++ {
-		for s = hash[h]; s != nil; s = s.Link {
-			if s.Name[0] != 'i' || s.Name != "init" {
-				continue
-			}
-			if s.Def == nil {
-				continue
-			}
+	for _, s := range initSyms {
+		if s.Def != nil {
 			return true
 		}
 	}
@@ -161,22 +155,10 @@
 	r = list(r, a)
 
 	// (7)
-	var s *Sym
-	for h := uint32(0); h < NHASH; h++ {
-		for s = hash[h]; s != nil; s = s.Link {
-			if s.Name[0] != 'i' || s.Name != "init" {
-				continue
-			}
-			if s.Def == nil {
-				continue
-			}
-			if s == initsym {
-				continue
-			}
-
+	for _, s := range initSyms {
+		if s.Def != nil && s != initsym {
 			// could check that it is fn of no args/returns
 			a = Nod(OCALL, s.Def, nil)
-
 			r = list(r, a)
 		}
 	}
@@ -188,7 +170,7 @@
 	// could check that it is fn of no args/returns
 	for i := 1; ; i++ {
 		namebuf = fmt.Sprintf("init.%d", i)
-		s = Lookup(namebuf)
+		s := Lookup(namebuf)
 		if s.Def == nil {
 			break
 		}
diff --git a/src/cmd/internal/gc/lex.go b/src/cmd/internal/gc/lex.go
index e447f4c..32f7240 100644
--- a/src/cmd/internal/gc/lex.go
+++ b/src/cmd/internal/gc/lex.go
@@ -1377,7 +1377,7 @@
 	cp = nil
 	ungetc(c)
 
-	s = Lookup(lexbuf.String())
+	s = LookupBytes(lexbuf.Bytes())
 	switch s.Lexical {
 	case LIGNORE:
 		goto l0
@@ -3120,36 +3120,33 @@
 		if pkgname != localpkg.Name {
 			Yyerror("package %s; expected %s", pkgname, localpkg.Name)
 		}
-		var s *Sym
-		for h := int32(0); h < NHASH; h++ {
-			for s = hash[h]; s != nil; s = s.Link {
-				if s.Def == nil || s.Pkg != localpkg {
-					continue
+		for _, s := range localpkg.Syms {
+			if s.Def == nil {
+				continue
+			}
+			if s.Def.Op == OPACK {
+				// throw away top-level package name leftover
+				// from previous file.
+				// leave s->block set to cause redeclaration
+				// errors if a conflicting top-level name is
+				// introduced by a different file.
+				if s.Def.Used == 0 && nsyntaxerrors == 0 {
+					pkgnotused(int(s.Def.Lineno), s.Def.Pkg.Path, s.Name)
 				}
-				if s.Def.Op == OPACK {
-					// throw away top-level package name leftover
-					// from previous file.
-					// leave s->block set to cause redeclaration
-					// errors if a conflicting top-level name is
-					// introduced by a different file.
-					if s.Def.Used == 0 && nsyntaxerrors == 0 {
-						pkgnotused(int(s.Def.Lineno), s.Def.Pkg.Path, s.Name)
-					}
-					s.Def = nil
-					continue
+				s.Def = nil
+				continue
+			}
+
+			if s.Def.Sym != s {
+				// throw away top-level name left over
+				// from previous import . "x"
+				if s.Def.Pack != nil && s.Def.Pack.Used == 0 && nsyntaxerrors == 0 {
+					pkgnotused(int(s.Def.Pack.Lineno), s.Def.Pack.Pkg.Path, "")
+					s.Def.Pack.Used = 1
 				}
 
-				if s.Def.Sym != s {
-					// throw away top-level name left over
-					// from previous import . "x"
-					if s.Def.Pack != nil && s.Def.Pack.Used == 0 && nsyntaxerrors == 0 {
-						pkgnotused(int(s.Def.Pack.Lineno), s.Def.Pack.Pkg.Path, "")
-						s.Def.Pack.Used = 1
-					}
-
-					s.Def = nil
-					continue
-				}
+				s.Def = nil
+				continue
 			}
 		}
 	}
diff --git a/src/cmd/internal/gc/reflect.go b/src/cmd/internal/gc/reflect.go
index a1ddf77..797f62a 100644
--- a/src/cmd/internal/gc/reflect.go
+++ b/src/cmd/internal/gc/reflect.go
@@ -1246,12 +1246,9 @@
 	}
 
 	// generate import strings for imported packages
-	var p *Pkg
-	for i := 0; i < len(phash); i++ {
-		for p = phash[i]; p != nil; p = p.Link {
-			if p.Direct != 0 {
-				dimportpath(p)
-			}
+	for _, p := range pkgs {
+		if p.Direct != 0 {
+			dimportpath(p)
 		}
 	}
 
diff --git a/src/cmd/internal/gc/subr.go b/src/cmd/internal/gc/subr.go
index 7449dad..675befc 100644
--- a/src/cmd/internal/gc/subr.go
+++ b/src/cmd/internal/gc/subr.go
@@ -275,54 +275,54 @@
 	return lno
 }
 
-func stringhash(p string) uint32 {
-	var c int
-
-	h := uint32(0)
-	for {
-		c, p = intstarstringplusplus(p)
-		if c == 0 {
-			break
-		}
-		h = h*PRIME1 + uint32(c)
-	}
-
-	if int32(h) < 0 {
-		h = -h
-		if int32(h) < 0 {
-			h = 0
-		}
-	}
-
-	return h
+func Lookup(name string) *Sym {
+	return localpkg.Lookup(name)
 }
 
-func Lookup(name string) *Sym {
-	return Pkglookup(name, localpkg)
+func LookupBytes(name []byte) *Sym {
+	return localpkg.LookupBytes(name)
+}
+
+var initSyms []*Sym
+
+var nopkg = new(Pkg)
+
+func (pkg *Pkg) Lookup(name string) *Sym {
+	if pkg == nil {
+		pkg = nopkg
+	}
+	if s := pkg.Syms[name]; s != nil {
+		return s
+	}
+
+	s := &Sym{
+		Name:    name,
+		Pkg:     pkg,
+		Lexical: LNAME,
+	}
+	if s.Name == "init" {
+		initSyms = append(initSyms, s)
+	}
+	if pkg.Syms == nil {
+		pkg.Syms = make(map[string]*Sym)
+	}
+	pkg.Syms[name] = s
+	return s
+}
+
+func (pkg *Pkg) LookupBytes(name []byte) *Sym {
+	if pkg == nil {
+		pkg = nopkg
+	}
+	if s := pkg.Syms[string(name)]; s != nil {
+		return s
+	}
+	str := internString(name)
+	return pkg.Lookup(str)
 }
 
 func Pkglookup(name string, pkg *Pkg) *Sym {
-	h := stringhash(name) % NHASH
-	c := int(name[0])
-	for s := hash[h]; s != nil; s = s.Link {
-		if int(s.Name[0]) != c || s.Pkg != pkg {
-			continue
-		}
-		if s.Name == name {
-			return s
-		}
-	}
-
-	s := new(Sym)
-	s.Name = name
-
-	s.Pkg = pkg
-
-	s.Link = hash[h]
-	hash[h] = s
-	s.Lexical = LNAME
-
-	return s
+	return pkg.Lookup(name)
 }
 
 func restrictlookup(name string, pkg *Pkg) *Sym {
@@ -335,35 +335,29 @@
 // find all the exported symbols in package opkg
 // and make them available in the current package
 func importdot(opkg *Pkg, pack *Node) {
-	var s *Sym
 	var s1 *Sym
 	var pkgerror string
 
 	n := 0
-	for h := uint32(0); h < NHASH; h++ {
-		for s = hash[h]; s != nil; s = s.Link {
-			if s.Pkg != opkg {
-				continue
-			}
-			if s.Def == nil {
-				continue
-			}
-			if !exportname(s.Name) || strings.ContainsRune(s.Name, 0xb7) { // 0xb7 = center dot
-				continue
-			}
-			s1 = Lookup(s.Name)
-			if s1.Def != nil {
-				pkgerror = fmt.Sprintf("during import %q", opkg.Path)
-				redeclare(s1, pkgerror)
-				continue
-			}
-
-			s1.Def = s.Def
-			s1.Block = s.Block
-			s1.Def.Pack = pack
-			s1.Origpkg = opkg
-			n++
+	for _, s := range opkg.Syms {
+		if s.Def == nil {
+			continue
 		}
+		if !exportname(s.Name) || strings.ContainsRune(s.Name, 0xb7) { // 0xb7 = center dot
+			continue
+		}
+		s1 = Lookup(s.Name)
+		if s1.Def != nil {
+			pkgerror = fmt.Sprintf("during import %q", opkg.Path)
+			redeclare(s1, pkgerror)
+			continue
+		}
+
+		s1.Def = s.Def
+		s1.Block = s.Block
+		s1.Def.Pack = pack
+		s1.Origpkg = opkg
+		n++
 	}
 
 	if n == 0 {
@@ -3583,19 +3577,19 @@
 	return s
 }
 
+var pkgMap = make(map[string]*Pkg)
+var pkgs []*Pkg
+
 func mkpkg(path string) *Pkg {
-	h := int(stringhash(path) & uint32(len(phash)-1))
-	for p := phash[h]; p != nil; p = p.Link {
-		if p.Path == path {
-			return p
-		}
+	if p := pkgMap[path]; p != nil {
+		return p
 	}
 
 	p := new(Pkg)
 	p.Path = path
 	p.Prefix = pathtoprefix(path)
-	p.Link = phash[h]
-	phash[h] = p
+	pkgMap[path] = p
+	pkgs = append(pkgs, p)
 	return p
 }
 
diff --git a/src/cmd/internal/gc/typecheck.go b/src/cmd/internal/gc/typecheck.go
index 72c2bba..0ff8224 100644
--- a/src/cmd/internal/gc/typecheck.go
+++ b/src/cmd/internal/gc/typecheck.go
@@ -2659,21 +2659,16 @@
 /*
  * type check composite
  */
-func fielddup(n *Node, hash []*Node) {
+func fielddup(n *Node, hash map[string]bool) {
 	if n.Op != ONAME {
 		Fatal("fielddup: not ONAME")
 	}
-	s := n.Sym.Name
-	h := uint(stringhash(s) % uint32(len(hash)))
-	for a := hash[h]; a != nil; a = a.Ntest {
-		if a.Sym.Name == s {
-			Yyerror("duplicate field name in struct literal: %s", s)
-			return
-		}
+	name := n.Sym.Name
+	if hash[name] {
+		Yyerror("duplicate field name in struct literal: %s", name)
+		return
 	}
-
-	n.Ntest = hash[h]
-	hash[h] = n
+	hash[name] = true
 }
 
 func keydup(n *Node, hash []*Node) {
@@ -3019,8 +3014,7 @@
 				Yyerror("too few values in struct initializer")
 			}
 		} else {
-			var autohash [101]*Node
-			hash := inithash(n, autohash[:])
+			hash := make(map[string]bool)
 
 			// keyed list
 			var s *Sym
diff --git a/src/cmd/internal/gc/y.go b/src/cmd/internal/gc/y.go
index 3657771..e1871ff 100644
--- a/src/cmd/internal/gc/y.go
+++ b/src/cmd/internal/gc/y.go
@@ -1226,7 +1226,7 @@
 		{
 			if importpkg.Name == "" {
 				importpkg.Name = yyDollar[2].sym.Name
-				Pkglookup(yyDollar[2].sym.Name, nil).Npkg++
+				numImport[yyDollar[2].sym.Name]++
 			} else if importpkg.Name != yyDollar[2].sym.Name {
 				Yyerror("conflicting names %s and %s for package %q", importpkg.Name, yyDollar[2].sym.Name, importpkg.Path)
 			}